diff options
Diffstat (limited to 'modules-available/news')
-rw-r--r-- | modules-available/news/api.inc.php | 65 | ||||
-rw-r--r-- | modules-available/news/install.inc.php | 31 | ||||
-rw-r--r-- | modules-available/news/lang/de/template-tags.json | 2 | ||||
-rw-r--r-- | modules-available/news/lang/en/template-tags.json | 2 | ||||
-rw-r--r-- | modules-available/news/page.inc.php | 162 | ||||
-rw-r--r-- | modules-available/news/permissions/permissions.json | 12 | ||||
-rw-r--r-- | modules-available/news/templates/page-news.html | 18 |
7 files changed, 185 insertions, 107 deletions
diff --git a/modules-available/news/api.inc.php b/modules-available/news/api.inc.php index 851f31a8..3b56c70d 100644 --- a/modules-available/news/api.inc.php +++ b/modules-available/news/api.inc.php @@ -4,23 +4,58 @@ header('Content-Type: application/xml; charset=utf-8'); $type = Request::any('type', 'news', 'string'); +if (Module::isAvailable('locations')) { + $locationId = Request::any('location', 0, 'int'); + if ($locationId === 0) { + $locationId = Location::getFromIp($_SERVER['REMOTE_ADDR']); + } + $locations = Location::getLocationRootChain($locationId); + $locations[] = 0; +} else { + $locations = [0]; +} + // Fetch news from DB -$row = Database::queryFirst('SELECT title, content, dateline FROM vmchooser_pages' - . ' WHERE type = :type AND expires > UNIX_TIMESTAMP() ORDER BY dateline DESC LIMIT 1', compact('type')); -if ($row !== false ) { +$res = Database::simpleQuery('SELECT title, locationid, content, dateline FROM vmchooser_pages + WHERE type = :type AND (locationid IS NULL OR locationid IN (:lids)) + AND expires > UNIX_TIMESTAMP() ORDER BY dateline DESC', [ + 'type' => $type, + 'lids' => $locations, + ]); - echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n"; - echo "<news>" . "\n"; - echo "\t" . '<headline>' . "\n"; - echo "\t\t" . htmlspecialchars($row['title']) . "\n"; - echo "\t" . '</headline>' . "\n"; - echo "\t" . "<info>" . "\n"; - echo "\t\t" . htmlspecialchars($row['content']) . "\n"; - echo "\t" . '</info>' . "\n"; - echo "\t" . "<date>" . "\n"; - echo "\t\t" . $row['dateline'] . "\n"; - echo "\t" . "</date>" . "\n"; - echo "</news>"; +// Get one for each location. As we sort by dateline and check expiry in the query, we only +// need one per location and then pick the first one that is set, as the locations are ordered +// by closest to furthest +$locations = array_flip($locations); +foreach ($res as $row) { + $lid = (int)$row['locationid']; + if (is_array($locations[$lid])) + continue; // Already have one + $locations[$lid] = $row; +} +// Pick first one +foreach ($locations as $row) { + if (is_array($row)) + break; +} + +if (is_array($row)) { + $title = htmlspecialchars($row['title']); + $content = htmlspecialchars($row['content']); + echo <<<EOF +<?xml version="1.0" encoding="UTF-8"?> +<news> + <headline> + $title + </headline> + <info> + $content + </info> + <date> + {$row['dateline']} + </date> +</news> +EOF; } else { // no news in DB, output a 'null' news xml diff --git a/modules-available/news/install.inc.php b/modules-available/news/install.inc.php index 88a20749..a60f7748 100644 --- a/modules-available/news/install.inc.php +++ b/modules-available/news/install.inc.php @@ -1,6 +1,6 @@ <?php -$res = array(); +$dbret = array(); @@ -10,8 +10,8 @@ if (tableExists('news')) { if (!tableRename('news', 'vmchooser_pages')) { finalResponse(UPDATE_FAILED, "Could not rename news to vmchooser_pages: " . Database::lastError()); } - $res[] = UPDATE_DONE; - if (false === Database::exec("ALTER TABLE `vmchooser_pages` ADD COLUMN type VARCHAR(10)")) { + $dbret[] = UPDATE_DONE; + if (false === Database::exec("ALTER TABLE `vmchooser_pages` ADD COLUMN type VARCHAR(10) CHARACTER SET ascii NOT NULL")) { EventLog::warning("Could not add type column to vmchooser_pages: " . Database::lastError()); } if (false === Database::exec("UPDATE `vmchooser_pages` SET `type` = 'news' WHERE 1")) { @@ -20,33 +20,46 @@ if (tableExists('news')) { } -$res[] = tableCreate('vmchooser_pages', " +$dbret[] = tableCreate('vmchooser_pages', " `newsid` int(10) unsigned NOT NULL AUTO_INCREMENT, `dateline` int(10) unsigned NOT NULL, `expires` int(10) unsigned NOT NULL, + `locationid` int(11) NULL, `title` varchar(200) DEFAULT NULL, - `content` text, - `type` varchar(10), + `content` text NOT NULL, + `type` varchar(10) CHARACTER SET ascii NOT NULL, PRIMARY KEY (`newsid`), - KEY `type` (`type`, `dateline`), - KEY `all3` (`type`, `expires`, `dateline`) + KEY `type` (`type`, `dateline`) "); if (tableGetIndex('vmchooser_pages', ['dateline']) !== false) { Database::exec('ALTER TABLE vmchooser_pages DROP KEY `dateline`'); Database::exec('ALTER TABLE vmchooser_pages ADD KEY `type` (`type`, `dateline`)'); + $dbret[] = UPDATE_DONE; } if (tableGetIndex('vmchooser_pages', ['type', 'expires', 'dateline']) === false) { Database::exec('ALTER TABLE vmchooser_pages ADD KEY `all3` (`type`, `expires`, `dateline`)'); + $dbret[] = UPDATE_DONE; } if (!tableHasColumn('vmchooser_pages', 'expires')) { Database::exec('ALTER TABLE vmchooser_pages ADD COLUMN `expires` int(10) unsigned NOT NULL AFTER `dateline`'); Database::exec('UPDATE vmchooser_pages SET expires = dateline + 86400 * 3650 WHERE expires = 0'); // ~10 Years + $dbret[] = UPDATE_DONE; } +if (!tableHasColumn('vmchooser_pages', 'locationid')) { + Database::exec('ALTER TABLE vmchooser_pages ADD COLUMN `locationid` int(11) NULL AFTER `expires`'); + $dbret[] = UPDATE_DONE; +} + +Database::exec('ALTER TABLE vmchooser_pages MODIFY `type` varchar(10) CHARACTER SET ascii NOT NULL'); + +$dbret[] = tableAddConstraint('vmchooser_pages', 'locationid', 'location', 'locationid', + 'ON UPDATE CASCADE ON DELETE CASCADE'); + // Create response for browser -if (in_array(UPDATE_DONE, $res)) { +if (in_array(UPDATE_DONE, $dbret)) { finalResponse(UPDATE_DONE, 'Tables created successfully'); } diff --git a/modules-available/news/lang/de/template-tags.json b/modules-available/news/lang/de/template-tags.json index 187d6cd3..dcd2f76f 100644 --- a/modules-available/news/lang/de/template-tags.json +++ b/modules-available/news/lang/de/template-tags.json @@ -2,12 +2,14 @@ "lang_confirmDelete": "Eintrag l\u00f6schen?", "lang_content": "Inhalt", "lang_created": "Erstellt", + "lang_editingForLocation": "Sie bearbeiten den Text f\u00fcr den oben angegebenen Ort (und ggf. alle Unterorte)", "lang_expires": "Ablauf", "lang_expiryDate": "Ablaufdatum", "lang_infiniteDuration": "L\u00e4uft nicht ab", "lang_introText": "Hier k\u00f6nnen Sie f\u00fcr verschiedene Bereiche des MiniLinux-Systems Neuigkeiten, Hilfetexte etc. definieren.", "lang_lastUpdate": "Letzte Aktualisierung", "lang_mainHeading": "Neuigkeiten und Hilfetexte", + "lang_overridenLocations": "Orte mit angepasstem Text", "lang_previousEntries": "Vorherige Eintr\u00e4ge", "lang_show": "Ansehen", "lang_title": "Titel", diff --git a/modules-available/news/lang/en/template-tags.json b/modules-available/news/lang/en/template-tags.json index 9fbc4a46..0d62bd08 100644 --- a/modules-available/news/lang/en/template-tags.json +++ b/modules-available/news/lang/en/template-tags.json @@ -2,12 +2,14 @@ "lang_confirmDelete": "Delete entry?", "lang_content": "Content", "lang_created": "Created", + "lang_editingForLocation": "You're editing this text only for the location given above (and any sub-locations it might have)", "lang_expires": "Expires", "lang_expiryDate": "Expiry date", "lang_infiniteDuration": "Never expires", "lang_introText": "Here you can define news, help and other informational texts to be displayed in different places in the running MiniLinux system.", "lang_lastUpdate": "Last update", "lang_mainHeading": "News and help texts", + "lang_overridenLocations": "Locations with customized text", "lang_previousEntries": "Previous entries", "lang_show": "Show", "lang_title": "Title", diff --git a/modules-available/news/page.inc.php b/modules-available/news/page.inc.php index 7a09d437..291f15fc 100644 --- a/modules-available/news/page.inc.php +++ b/modules-available/news/page.inc.php @@ -39,6 +39,10 @@ class Page_News extends Page * @var int Unix epoch date when the news expires. */ private $newsExpires = false; + /** + * @var int location id + */ + private $locationId = 0; /** @@ -63,30 +67,33 @@ class Page_News extends Page User::assertPermission('access-page'); /* and also the news (or help) with the given id */ - $newsId = Request::get('newsid', false, 'int'); - $pageType = Request::get('type', false, 'string'); - if ($pageType === false && $newsId === false) { - Util::redirect('?do=news&type=news'); + $newsId = Request::get('newsid', null, 'int'); + $pageType = Request::get('type', null, 'string'); + $this->locationId = Request::get('locationid', 0, 'int'); + if ($pageType === null && $newsId === null) { + Util::redirect('?do=news&type=news&locationid=' . $this->locationId); } - $this->pageType = $pageType === false ? 'news' : $pageType; - $this->loadNews($newsId, $pageType); + $this->pageType = $pageType ?? 'news'; + $this->loadNews($newsId); foreach (self::TYPES as $type => $entry) { - Dashboard::addSubmenu('?do=news&type=' . $type, Dictionary::translate('type_' . $type, true)); + Dashboard::addSubmenu('?do=news&type=' . $type . '&locationid=' . $this->locationId, + Dictionary::translate('type_' . $type)); } } else { $action = Request::post('action', false, 'string'); $pageType = Request::post('type', false, 'string'); + $this->locationId = Request::post('locationid', Request::REQUIRED_EMPTY, 'int'); if (!array_key_exists($pageType, self::TYPES)) { Message::addError('invalid-type', $pageType); - Util::redirect('?do=news'); + Util::redirect('?do=news&locationid=' . $this->locationId); } if ($action === 'save') { // save to DB - User::assertPermission("$pageType.save"); + User::assertPermission("$pageType.save", $this->locationId); if (!$this->saveNews($pageType)) { Message::addError('save-error'); } else { @@ -95,14 +102,14 @@ class Page_News extends Page } elseif ($action === 'delete') { // delete it - User::assertPermission("$pageType.delete"); - $this->delNews(Request::post('newsid', false, 'int'), $pageType); + User::assertPermission("$pageType.delete", $this->locationId); + $this->delNews(Request::post('newsid', Request::REQUIRED, 'int'), $pageType); } else { // unknown action, redirect user Message::addError('invalid-action', $action); } - Util::redirect('?do=news&type=' . $pageType); + Util::redirect('?do=news&type=' . $pageType . '&locationid=' . $this->locationId); } /* load summernote module if available */ @@ -119,10 +126,11 @@ class Page_News extends Page // fetch the list of the older news $NOW = time(); $lines = array(); + $str = $this->locationId === 0 ? 'IS NULL' : ' = ' . $this->locationId; $res = Database::simpleQuery("SELECT newsid, dateline, expires, title, content FROM vmchooser_pages - WHERE type = :type ORDER BY dateline DESC LIMIT 20", ['type' => $this->pageType]); + WHERE type = :type AND locationid $str ORDER BY dateline DESC LIMIT 20", ['type' => $this->pageType]); $foundActive = false; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($res as $row) { $row['dateline_s'] = Util::prettyTime($row['dateline']); $row['expires_s'] = $this->formatExpires($row['expires']); if ($row['newsid'] == $this->newsId) { @@ -141,7 +149,7 @@ class Page_News extends Page $data = array( 'withTitle' => self::TYPES[$this->pageType]['headline'], - 'newsTypeName' => Dictionary::translate('type_' . $this->pageType, true), + 'newsTypeName' => Dictionary::translate('type_' . $this->pageType), 'dateline_s' => Util::prettyTime($this->newsDateline), 'expires_s' => $this->formatExpires($this->newsExpires), 'currentContent' => $this->newsContent, @@ -169,10 +177,19 @@ class Page_News extends Page 'disabled' => 'disabled', ]; } + $data['locationid'] = $this->locationId; + if ($this->locationId > 0) { + $data['location_name'] = Location::getName($this->locationId); + } else { + // Superadmin can see all overridden locations + $data['overridden'] = Database::queryAll("SELECT DISTINCT l.locationid, l.locationname FROM vmchooser_pages + INNER JOIN location l USING (locationid) + WHERE expires > UNIX_TIMESTAMP() ORDER BY locationname ASC"); + } Render::addTemplate('page-news', $data); } - private function formatExpires($ts) + private function formatExpires(int $ts): string { if ($ts - 86400 * 365 * 5 > time()) return '-'; @@ -182,15 +199,12 @@ class Page_News extends Page /** * Loads the news with the given ID into the form. * - * @param int $newsId ID of the news to be shown. - * @param string $pageType type if news id is not given. - * - * @return bool true if loading that news worked + * @param ?int $newsId ID of the news to be shown, or latest if null */ - private function loadNews($newsId, $pageType) + private function loadNews(?int $newsId): void { // check to see if we need to request a specific newsid - if ($newsId !== false) { + if ($newsId !== null) { $row = Database::queryFirst('SELECT newsid, title, content, dateline, expires, type FROM vmchooser_pages WHERE newsid = :newsid LIMIT 1', [ 'newsid' => $newsId, @@ -199,74 +213,74 @@ class Page_News extends Page Message::addError('news-empty'); } } else { + $str = $this->locationId === 0 ? 'IS NULL' : ' = ' . $this->locationId; $row = Database::queryFirst("SELECT newsid, title, content, dateline, expires, type FROM vmchooser_pages - WHERE type = :type AND expires > UNIX_TIMESTAMP() ORDER BY dateline DESC LIMIT 1", [ - 'type' => $pageType, + WHERE type = :type AND locationid $str AND expires > UNIX_TIMESTAMP() ORDER BY dateline DESC LIMIT 1", [ + 'type' => $this->pageType, ]); } if ($row === false) - return false; + return; // fetch the news to be shown - if ($row !== false) { - $this->newsId = $row['newsid']; - $this->newsTitle = $row['title']; - $this->newsContent = $row['content']; - $this->newsDateline = (int)$row['dateline']; - $this->newsExpires = (int)$row['expires']; - $this->pageType = $row['type']; - } - return true; + $this->newsId = $row['newsid']; + $this->newsTitle = $row['title']; + $this->newsContent = $row['content']; + $this->newsDateline = (int)$row['dateline']; + $this->newsExpires = (int)$row['expires']; + $this->pageType = $row['type']; } /** * Save the given $newsTitle and $newsContent as POST'ed into the database. */ - private function saveNews($pageType) + private function saveNews(string $pageType): bool { // check if news content were set by the user $newsTitle = Request::post('news-title', '', 'string'); - $newsContent = Request::post('news-content', false, 'string'); + $newsContent = Request::post('news-content', Request::REQUIRED, 'string'); + $test = trim(html_entity_decode(strip_tags($newsContent), ENT_QUOTES, 'UTF-8')); + if (empty($test)) { + Message::addError('main.empty-field'); + return false; + } $infinite = (Request::post('infinite', '', 'string') !== ''); if ($infinite) { - $expires = strtotime('+10 years 0:00'); + $expires = strtotime('+20 years 0:00'); } else { $expires = strtotime(Request::post('enddate', 'today', 'string') . ' ' - . Request::post('endtime', '23:59', 'string')); + . Request::post('endtime', '23:59', 'string')); } - if (!empty($newsContent)) { - // we got title and content, save it to DB - // dup check first - $row = Database::queryFirst('SELECT newsid FROM vmchooser_pages - WHERE content = :content AND type = :type LIMIT 1', [ - 'content' => $newsContent, - 'type' => $pageType, - ]); - if ($row !== false) { - Database::exec('UPDATE vmchooser_pages SET dateline = :dateline, expires = :expires, title = :title - WHERE newsid = :newsid LIMIT 1', [ - 'newsid' => $row['newsid'], - 'dateline' => time(), - 'expires' => $expires, - 'title' => $newsTitle, - ]); - return true; - } - // new one - Database::exec("INSERT INTO vmchooser_pages (dateline, expires, title, content, type) - VALUES (:dateline, :expires, :title, :content, :type)", array( + $str = $this->locationId === 0 ? 'IS NULL' : ' = ' . $this->locationId; + // we got title and content, save it to DB + // dup check first + $row = Database::queryFirst("SELECT newsid FROM vmchooser_pages + WHERE content = :content AND type = :type AND locationid $str LIMIT 1", [ + 'content' => $newsContent, + 'type' => $pageType, + ]); + if ($row !== false) { + Database::exec('UPDATE vmchooser_pages SET dateline = :dateline, expires = :expires, title = :title + WHERE newsid = :newsid LIMIT 1', [ + 'newsid' => $row['newsid'], 'dateline' => time(), 'expires' => $expires, 'title' => $newsTitle, - 'content' => $newsContent, - 'type' => $pageType, - )); - + ]); return true; } + // new one + Database::exec("INSERT INTO vmchooser_pages (dateline, expires, locationid, title, content, type) + VALUES (:dateline, :expires, :locationid, :title, :content, :type)", array( + 'dateline' => time(), + 'expires' => $expires, + 'locationid' => $this->locationId === 0 ? null : $this->locationId, + 'title' => $newsTitle, + 'content' => $newsContent, + 'type' => $pageType, + )); - Message::addError('main.empty-field'); - return false; + return true; } /** @@ -275,18 +289,12 @@ class Page_News extends Page * @param int $newsId ID of the entry to be deleted. * @param string $pageType type of news to be deleted. Must match the ID, otherwise do nothing. */ - private function delNews($newsId, $pageType) + private function delNews(int $newsId, string $pageType): void { - // sanity check: is newsId even numeric? - if (!is_numeric($newsId)) { - Message::addError('main.value-invalid', 'newsid', $newsId); - } else { - // check passed - do delete - Database::exec('DELETE FROM vmchooser_pages WHERE newsid = :newsid AND type = :type LIMIT 1', array( - 'newsid' => $newsId, - 'type' => $pageType, - )); - Message::addSuccess('news-del-success'); - } + Database::exec('DELETE FROM vmchooser_pages WHERE newsid = :newsid AND type = :type LIMIT 1', array( + 'newsid' => $newsId, + 'type' => $pageType, + )); + Message::addSuccess('news-del-success'); } } diff --git a/modules-available/news/permissions/permissions.json b/modules-available/news/permissions/permissions.json index 4ff1a01b..83fad86c 100644 --- a/modules-available/news/permissions/permissions.json +++ b/modules-available/news/permissions/permissions.json @@ -3,21 +3,21 @@ "location-aware": false }, "help.delete": { - "location-aware": false + "location-aware": true }, "help.save": { - "location-aware": false + "location-aware": true }, "news.delete": { - "location-aware": false + "location-aware": true }, "news.save": { - "location-aware": false + "location-aware": true }, "login-news.delete": { - "location-aware": false + "location-aware": true }, "login-news.save": { - "location-aware": false + "location-aware": true } }
\ No newline at end of file diff --git a/modules-available/news/templates/page-news.html b/modules-available/news/templates/page-news.html index 1c944cb8..5ace6e35 100644 --- a/modules-available/news/templates/page-news.html +++ b/modules-available/news/templates/page-news.html @@ -2,12 +2,30 @@ <p>{{lang_introText}}</p> +{{#overridden.0}} + <div class="pull-right"> + <h3>{{lang_overridenLocations}}</h3> + <ul> + {{#overridden}} + <li><a href="?do=news&type={{type}}&locationid={{locationid}}">{{locationname}}</a></li> + {{/overridden}} + </ul> + </div> +{{/overridden.0}} + <h2>{{newsTypeName}}</h2> +{{#locationid}} + <h3>{{location_name}}</h3> + <div class="alert alert-info">{{lang_editingForLocation}}</div> +{{/locationid}} +<div class="clearfix"></div> + <form action="?do=news" method="post"> <input type="hidden" name="token" value="{{token}}"> <input type="hidden" name="type" value="{{type}}"> <input type="hidden" name="action" value="save"> + <input type="hidden" name="locationid" value="{{locationid}}"> {{#withTitle}} <div class="form-group"> <label for="news-title-id">{{lang_title}}</label> |