diff --git a/composer.json b/composer.json
index 0ba98be6..6272bf01 100644
--- a/composer.json
+++ b/composer.json
@@ -26,6 +26,7 @@
"inc/lock.php",
"inc/queue.php",
"inc/polyfill.php",
+ "inc/announcements.php",
"inc/functions.php"
]
},
diff --git a/inc/announcements.php b/inc/announcements.php
new file mode 100644
index 00000000..092d35de
--- /dev/null
+++ b/inc/announcements.php
@@ -0,0 +1,155 @@
+bindValue(':mod', $mod_id);
+ $query->bindValue(':time', time());
+ if ($announcement !== '') {
+ $announcement = escape_markup_modifiers($announcement);
+ markup($announcement);
+ $query->bindValue(':text', $announcement);
+ } else
+ error(sprintf($config['error']['required'], "Announcement"));
+
+ $query->execute() or error(db_error($query));
+
+ modLog("Created a new announcement: " . utf8tohtml($announcement));
+ self::buildAnnouncements();
+ }
+
+ static public function edit_announcement($id, $announcement) {
+ global $mod, $config;
+
+ $query = prepare(sprintf("UPDATE ``announcements`` SET `text` = :text WHERE `id` = %d", (int)$id));
+ if ($announcement !== '') {
+ $teannouncementxt = escape_markup_modifiers($announcement);
+ markup($announcement);
+ $query->bindValue(':text', $announcement);
+ } else
+ error(sprintf($config['error']['required'], "Announcement"));
+
+ $query->execute() or error(db_error($query));
+
+ modLog("Edited announcement #" . $id . " - New Text: " . utf8tohtml($announcement));
+ self::buildAnnouncements();
+ }
+
+ static public function delete_announcement($id) {
+ $query = prepare(sprintf("DELETE FROM ``announcements`` WHERE `id` = %d", (int)$id));
+ $query->execute() or error(db_error($query));
+
+ modLog("Deleted announcement #" . $id);
+ self::buildAnnouncements();
+ }
+
+ static public function buildAnnouncements() {
+ self::buildShortAnnouncementTable();
+ self::buildAnnouncementPages();
+ }
+
+ static public function buildShortAnnouncementTable() {
+ global $config;
+
+ $count = $config['announcements']['show_count'];
+ $query = query("SELECT `text`,`date` FROM ``announcements`` ORDER BY `date` DESC" . (($count === false)?"":" LIMIT " . (int)$count)) or error(db_error($query));
+ $announcements = $query->fetchAll(PDO::FETCH_ASSOC);
+
+ foreach ($announcements as &$announce) {
+ $announce['date_formated'] = strftime($config['announcements']['date_format'], $announce['date']);
+ }
+
+ $announcements_short = Element('announcements.html', array(
+ 'announcements' => $announcements,
+ ));
+
+ file_write($config['dir']['home'] . "templates/generated/announcements_short.html", $announcements_short);
+ }
+
+
+ static public function buildAnnouncementPages() {
+ global $config;
+
+ // Generate page for full list of announcements
+ if($config['announcements']['page'])
+ {
+ // Generate JSON file for full list of announcements
+ //file_write($config['dir']['home'] . "announcements.json", self::gen_public_json($config['announcements']['date_format'], false));
+
+ $query = query("SELECT ``announcements``.* FROM ``announcements``
+ ORDER BY `date` DESC") or error(db_error($query));
+ $announcements = $query->fetchAll(PDO::FETCH_ASSOC);
+
+ foreach ($announcements as &$announce) {
+ $announce['date_formated'] = strftime($config['announcements']['date_format'], $announce['date']);
+ }
+
+ // Generate page for full list of announcements
+ $announcement_page = Element('page.html', array(
+ 'config' => $config,
+ 'mod' => false,
+ 'hide_dashboard_link' => true,
+ 'boardlist' => createBoardList(false),
+ 'title' => _("Announcements"),
+ 'subtitle' => "",
+ 'nojavascript' => true,
+ 'body' => Element('announcements_list.html', array(
+ 'announcements' => $announcements,
+ 'mod' => false,
+ 'token_json' => false,
+ ))
+ ));
+ file_write($config['dir']['home'] . $config['announcements']['page_html'], $announcement_page);
+ }
+ }
+
+/* Might be used later for mobile API
+ static public function stream_json($out = false, $filter_staff = false, $date_format, $count = false) {
+ $query = query("SELECT ``announcements``.*, `username` FROM ``announcements``
+ LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`
+ ORDER BY `date` DESC" . (($count === false)?"":" LIMIT " . (int)$count)) or error(db_error($query));
+ $announcements = $query->fetchAll(PDO::FETCH_ASSOC);
+
+ $out ? fputs($out, "[") : print("[");
+
+ // Last entry for json end check
+ $end = end($announcements);
+
+ foreach ($announcements as &$announce) {
+
+ if($filter_staff)
+ $announce['username'] = '?';
+
+ $announce['date_formated'] = strftime($date_format, $announce['date']);
+
+ $json = json_encode($announce);
+ $out ? fputs($out, $json) : print($json);
+
+ if ($announce['id'] != $end['id']) {
+ $out ? fputs($out, ",") : print(",");
+ }
+ }
+
+ $out ? fputs($out, "]") : print("]");
+ }
+
+ // Returns json content to be written to json file.
+ static public function gen_public_json($date_format, $count = false) {
+ ob_start();
+ self::stream_json(false, true, $date_format, $count);
+ $out = ob_get_contents();
+ ob_end_clean();
+ return $out;
+ }
+*/
+};
+
+?>
\ No newline at end of file
diff --git a/inc/config.php b/inc/config.php
index 764bbda8..0a119462 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -946,6 +946,26 @@
// Allow unfiltered HTML in board subtitle. This is useful for placing icons and links.
$config['allow_subtitle_html'] = false;
+/*
+ * ====================
+ * Announcements settings
+ * ====================
+ */
+ // Show small list of announcements.
+ $config['announcements']['show'] = true;
+
+ // Number of announcements to include in short announcements lists.
+ $config['announcements']['show_count'] = 3;
+
+ // Date format for announcements.
+ $config['announcements']['date_format'] = '%m/%d/%Y';
+
+ // Create full announcements page.
+ $config['announcements']['page'] = true;
+
+ // Filename for file to hold complete list of announcements
+ $config['announcements']['page_html'] = "announcements.html";
+
/*
* ====================
* Display settings
@@ -1676,6 +1696,8 @@
$config['mod']['public_ban'] = MOD;
// Manage and install themes for homepage
$config['mod']['themes'] = ADMIN;
+ // Create or delete announcements
+ $config['mod']['announcements'] = ADMIN;
// Post news entries
$config['mod']['news'] = ADMIN;
// Custom name when posting news
diff --git a/inc/mod/pages.php b/inc/mod/pages.php
index 28a6e4df..8e182e96 100644
--- a/inc/mod/pages.php
+++ b/inc/mod/pages.php
@@ -963,6 +963,41 @@ function mod_page_ip($cip) {
mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($cip)), 'mod/view_ip.html', $args, $args['hostname'] ?? null);
}
+function mod_announcements() {
+ global $config;
+ global $mod;
+
+ if (!hasPermission($config['mod']['announcements']))
+ error($config['error']['noaccess']);
+
+ // Add, edit, or delete announcement
+ if (isset($_POST['announcement'], $_POST['id'])) {
+ if ($_POST['id'] == '-1') {
+ Announcements::new_announcement($_POST['announcement']);
+ } else if ($_POST['announcement'] == '') {
+ Announcements::delete_announcement($_POST['id']);
+ } else {
+ Announcements::edit_announcement($_POST['id'], $_POST['announcement']);
+ }
+ }
+
+ $query = query("SELECT ``announcements``.*, `username` FROM ``announcements``
+ LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`
+ ORDER BY `date` DESC") or error(db_error($query));
+ $announcements = $query->fetchAll(PDO::FETCH_ASSOC);
+
+ foreach ($announcements as &$announce) {
+ $announce['date_formated'] = strftime($config['announcements']['date_format'], $announce['date']);
+ }
+
+ // Display announcement page
+ mod_page(_('Announcements list'), 'announcements_list.html', array(
+ 'announcements' => $announcements,
+ 'mod' => $mod,
+ 'token' => make_secure_link_token('announcements'),
+ ));
+}
+
function mod_ban() {
global $config;
diff --git a/install.php b/install.php
index 09aa1671..0c3b99dd 100644
--- a/install.php
+++ b/install.php
@@ -636,6 +636,14 @@ if (file_exists($config['has_installed'])) {
PRIMARY KEY (`cookie`,`extra`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;') or error(db_error());
case '5.1.4':
+ query('CREATE TABLE IF NOT EXISTS `announcements` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `creator` int(10) NOT NULL,
+ `date` int(10) NOT NULL,
+ `text` text NOT NULL,
+ PRIMARY KEY (`id`)
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+ ') or error(db_error());
query('CREATE TABLE IF NOT EXISTS ``filehashes`` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`board` varchar(58) NOT NULL,
@@ -878,6 +886,13 @@ if ($step == 0) {
'required' => true,
'message' => 'vichan does not have permission to create directories (boards) here. You will need to chmod
(or operating system equivalent) appropriately.'
),
+ array(
+ 'category' => 'File permissions',
+ 'name' => getcwd() . '/templates/generated',
+ 'result' => is_dir('templates/generated') && is_writable('templates/generated'),
+ 'required' => true,
+ 'message' => 'You must give vichan permission to write to the templates/generated
directory.'
+ ),
array(
'category' => 'File permissions',
'name' => getcwd() . '/templates/cache',
@@ -958,6 +973,9 @@ if ($step == 0) {
$more = $_POST['more'];
unset($_POST['more']);
+ // Generate empty templates that are assumed to exist by other templates
+ file_write("templates/generated/announcements_short.html", "");
+
$instance_config =
'<'.'?php
diff --git a/install.sql b/install.sql
index b2037bee..9126ed17 100644
--- a/install.sql
+++ b/install.sql
@@ -17,6 +17,19 @@ SET time_zone = "+00:00";
-- --------------------------------------------------------
+--
+-- Table structure for table `announcements`
+--
+
+CREATE TABLE IF NOT EXISTS `announcements` (
+ `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `creator` int(10) NOT NULL,
+ `date` int(10) NOT NULL,
+ `text` text NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+
+-- --------------------------------------------------------
--
-- Table structure for table `antispam`
--
diff --git a/mod.php b/mod.php
index bdc56149..d83c4058 100644
--- a/mod.php
+++ b/mod.php
@@ -49,6 +49,8 @@ $pages = array(
'/noticeboard' => 'secure_POST noticeboard', // view noticeboard
'/noticeboard/(\d+)' => 'secure_POST noticeboard', // view noticeboard
'/noticeboard/delete/(\d+)' => 'secure noticeboard_delete', // delete from noticeboard
+
+ '/announcements' => 'secure_POST announcements', // announcement list
'/edit/(\%b)' => 'secure_POST edit_board', // edit board details
'/new-board' => 'secure_POST new_board', // create a new board
diff --git a/stylesheets/style.css b/stylesheets/style.css
index ba95f5d4..d50f2a97 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -532,6 +532,30 @@ div.blotter {
text-align: center;
}
+table.announcements {
+ text-align: center;
+ width: 480px;
+ margin-left: auto;
+ margin-right: auto;
+ font-size: 0.8em;
+}
+table.announcements thead td {
+ border-bottom: 1px solid rgb(63, 63, 63);
+ border-bottom: 1px solid rgba(0, 0, 0, .4);
+ -webkit-background-clip: padding-box; /* for Safari */
+ background-clip: padding-box; /* for IE9+, Firefox 4+, Opera, Chrome */
+}
+table.announcements tbody {
+ text-align: left;
+}
+table.announcements tbody td:first-child {
+ text-align: right;
+ vertical-align: top;
+}
+table.announcements tfoot td {
+ text-align: right;
+}
+
table.mod.config-editor {
font-size: 9pt;
width: 100%;
diff --git a/templates/announcements.html b/templates/announcements.html
new file mode 100644
index 00000000..e62af380
--- /dev/null
+++ b/templates/announcements.html
@@ -0,0 +1,21 @@
+
+ | |
{{ announcement.date_formated }} | {{ announcement.text }} | +
+ [Show All] + | +
To remove an announcement, make it empty then press Update.
+You will need to {% trans 'Rebuild' %} pages after this to display the new announcements.
+Date | +Announcement | +Staff | +Action | +
---|
Date | +Announcement | +
---|---|
{{ announcement.date_formated }} | +{{ announcement.text }} | +