<?php
class Page_News extends Page
{
private $hasSummernote = false;
const TYPES = [
// Dictionary::translate('type_news');
'news' => ['headline' => true],
// Dictionary::translate('type_help');
'help' => ['headline' => false],
// Dictionary::translate('type_login-news');
'login-news' => ['headline' => false],
];
private $pageType = false;
/*
* Member variables needed to represent a news entry.
*/
/**
* @var int ID of the news entry attributed by the database.
*/
private $newsId = false;
/**
* @var string Title of the entry.
*/
private $newsTitle = false;
/**
* @var string HTML news content
*/
private $newsContent = false;
/**
* @var int Unix epoch date of the news' creation.
*/
private $newsDateline = false;
/**
* @var int Unix epoch date when the news expires.
*/
private $newsExpires = false;
/**
* @var int location id
*/
private $locationId = 0;
/**
* Implementation of the abstract doPreprocess function.
*
* Checks if the user is logged in and processes any
* action if one was specified in the request.
*/
protected function doPreprocess()
{
// load user, we will need it later
User::load();
if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
// check which action we need to do
if (!Request::isPost()) {
User::assertPermission('access-page');
/* and also the news (or help) with the given id */
$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 ?? 'news';
$this->loadNews($newsId);
foreach (self::TYPES as $type => $entry) {
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&locationid=' . $this->locationId);
}
if ($action === 'save') {
// save to DB
User::assertPermission("$pageType.save", $this->locationId);
if (!$this->saveNews($pageType)) {
Message::addError('save-error');
} else {
Message::addSuccess('news-save-success');
}
} elseif ($action === 'delete') {
// delete it
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 . '&locationid=' . $this->locationId);
}
/* load summernote module if available */
$this->hasSummernote = Module::isAvailable('summernote');
}
/**
* Implementation of the abstract doRender function.
*
* Fetch the list of news from the database and paginate it.
*/
protected function doRender()
{
// 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 AND locationid $str ORDER BY dateline DESC LIMIT 20", ['type' => $this->pageType]);
$foundActive = false;
foreach ($res as $row) {
$row['dateline_s'] = Util::prettyTime($row['dateline']);
$row['expires_s'] = $this->formatExpires($row['expires']);
if ($row['newsid'] == $this->newsId) {
$row['active'] = true;
}
if ($row['expires'] < $NOW) {
$row['muted'] = 'text-muted';
} elseif (!$foundActive) {
$row['live'] = 'active';
$foundActive = true;
}
$row['content'] = substr(strip_tags(str_replace('>', '> ', $row['content'])), 0, 160);
$lines[] = $row;
}
$data = array(
'withTitle' => self::TYPES[$this->pageType]['headline'],
'newsTypeName' => Dictionary::translate('type_' . $this->pageType),
'dateline_s' => Util::prettyTime($this->newsDateline),
'expires_s' => $this->formatExpires($this->newsExpires),
'currentContent' => $this->newsContent,
'currentTitle' => $this->newsTitle,
'type' => $this->pageType,
'list' => $lines,
'hasSummernote' => $this->hasSummernote,
);
$validity = ceil(($this->newsExpires - $NOW) / 3600);
if ($this->newsExpires === false || $validity > 24 * 365 * 5) {
$data['infinite_checked'] = 'checked';
$this->newsExpires = strtotime('+7 days 00:00:00');
}
$data['enddate'] = date('Y-m-d', $this->newsExpires);
$data['endtime'] = date('H:i', $this->newsExpires);
if (!User::hasPermission($this->pageType . '.save')) {
$data['save'] = [
'readonly' => 'readonly',
'disabled' => 'disabled',
];
}
if (!User::hasPermission($this->pageType . '.delete')) {
$data['delete'] = [
'readonly' => 'readonly',
'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(int $ts): string
{
if ($ts - 86400 * 365 * 5 > time())
return '-';
return Util::prettyTime($ts);
}
/**
* Loads the news with the given ID into the form.
*
* @param ?int $newsId ID of the news to be shown, or latest if null
*/
private function loadNews(?int $newsId): void
{
// check to see if we need to request a specific newsid
if ($newsId !== null) {
$row = Database::queryFirst('SELECT newsid, title, content, dateline, expires, type FROM vmchooser_pages
WHERE newsid = :newsid LIMIT 1', [
'newsid' => $newsId,
]);
if ($row === false) {
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 locationid $str AND expires > UNIX_TIMESTAMP() ORDER BY dateline DESC LIMIT 1", [
'type' => $this->pageType,
]);
}
if ($row === false)
return;
// fetch the news to be shown
$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(string $pageType): bool
{
// check if news content were set by the user
$newsTitle = Request::post('news-title', '', '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('+20 years 0:00');
} else {
$expires = strtotime(Request::post('enddate', 'today', 'string') . ' '
. Request::post('endtime', '23:59', 'string'));
}
$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,
]);
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,
));
return true;
}
/**
* Delete the news entry with ID $newsId.
*
* @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(int $newsId, string $pageType): void
{
Database::exec('DELETE FROM vmchooser_pages WHERE newsid = :newsid AND type = :type LIMIT 1', array(
'newsid' => $newsId,
'type' => $pageType,
));
Message::addSuccess('news-del-success');
}
}