summaryrefslogtreecommitdiff
path: root/templates/themes/semirand/theme.php
diff options
context:
space:
mode:
authorMatěj Grabovský <mgrabovsky@yahoo.com>2015-06-02 15:28:06 +0200
committerMatěj Grabovský <mgrabovsky@yahoo.com>2015-07-03 16:13:16 +0200
commit7c6eef81c55d6f5ccfa6ebba759afcec7b79115d (patch)
tree8c5dbfee3d267700ff5a3c53a486eee607368c7d /templates/themes/semirand/theme.php
parentb25b443e55cf20420231e82e5ffdaeff6db595d5 (diff)
Add a semirandom theme
This theme displays random threads intermixed with recently bumped ones. The number of each is configurable.
Diffstat (limited to 'templates/themes/semirand/theme.php')
-rw-r--r--templates/themes/semirand/theme.php254
1 files changed, 254 insertions, 0 deletions
diff --git a/templates/themes/semirand/theme.php b/templates/themes/semirand/theme.php
new file mode 100644
index 00000000..1acecbfb
--- /dev/null
+++ b/templates/themes/semirand/theme.php
@@ -0,0 +1,254 @@
+<?php
+
+ require 'info.php';
+
+ /**
+ * Generate the board's HTML and move it and its JavaScript in place, whence
+ * it's served
+ */
+ function semirand_build($action, $settings) {
+ global $config;
+
+ if ($action !== 'all' && $action !== 'post' && $action !== 'post-thread' &&
+ $action !== 'post-delete')
+ {
+ return;
+ }
+
+ if ($config['smart_build']) {
+ file_unlink($settings['uri'] . '/index.html');
+ } else {
+ $semirand = new semirand($settings);
+
+ // Copy the generated board HTML to its place
+ file_write($settings['uri'] . '/index.html', $semirand->build());
+ file_write($settings['uri'] . '/semirand.js',
+ Element('themes/semirand/semirand.js', array()));
+ }
+ }
+
+ /**
+ * Encapsulation of the theme's internals
+ */
+ class semirand {
+ private $settings;
+
+ function __construct($settings) {
+ $this->settings = $this->parseSettings($settings);
+ }
+
+ /**
+ * Parse and validate configuration parameters passed from the UI
+ */
+ private function parseSettings($settings) {
+ if (!is_numeric($settings['thread_limit']) ||
+ !is_numeric($settings['random_count']) ||
+ !is_numeric($settings['recent_count']))
+ {
+ error('Invalid configuration parameters.', true);
+ }
+
+ $settings['exclude'] = explode(' ', $settings['exclude']);
+ $settings['thread_limit'] = intval($settings['thread_limit']);
+ $settings['random_count'] = intval($settings['random_count']);
+ $settings['recent_count'] = intval($settings['recent_count']);
+
+ if ($settings['thread_limit'] < 1 ||
+ $settings['random_count'] < 1 ||
+ $settings['recent_count'] < 1)
+ {
+ error('Invalid configuration parameters.', true);
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Obtain list of all threads from all non-excluded boards
+ */
+ private function fetchThreads() {
+ $query = '';
+ $boards = listBoards(true);
+
+ foreach ($boards as $b) {
+ if (in_array($b, $this->settings['exclude']))
+ continue;
+ // Threads are those posts that have no parent thread
+ $query .= "SELECT *, '$b' AS `board` FROM ``posts_$b`` " .
+ "WHERE `thread` IS NULL UNION ALL ";
+ }
+
+ $query = preg_replace('/UNION ALL $/', 'ORDER BY `bump` DESC', $query);
+ $result = query($query) or error(db_error());
+
+ return $result->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Retrieve all replies to a given thread
+ */
+ private function fetchReplies($board, $thread_id) {
+ $query = prepare("SELECT * FROM ``posts_$board`` WHERE `thread` = :id");
+ $query->bindValue(':id', $thread_id, PDO::PARAM_INT);
+ $query->execute() or error(db_error($query));
+
+ return $query->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ /**
+ * Intersperse random threads between those in bump order
+ */
+ private function shuffleThreads($threads) {
+ $random_count = $this->settings['random_count'];
+ $recent_count = $this->settings['recent_count'];
+ $total = count($threads);
+
+ // Storage for threads that will be randomly interspersed
+ $shuffled = array();
+
+ // Ratio of bumped / all threads
+ $topRatio = $recent_count / ($recent_count + $random_count);
+ // Shuffle the bottom half of all threads
+ $random = array_splice($threads, floor($total * $topRatio));
+ shuffle($random);
+
+ // Merge the random and sorted threads into one sequence. The pattern
+ // starts at random threads
+ while (!empty($threads)) {
+ $shuffled = array_merge($shuffled,
+ array_splice($random, 0, $random_count),
+ array_splice($threads, 0, $recent_count));
+ }
+
+ return $shuffled;
+ }
+
+ /**
+ * Build the HTML of a single thread in the catalog
+ */
+ private function buildOne($post, $mod = false) {
+ global $config;
+
+ openBoard($post['board']);
+ $thread = new Thread($post, $mod ? '?/' : $config['root'], $mod);
+ $replies = $this->fetchReplies($post['board'], $post['id']);
+ // Number of replies to a thread that are displayed beneath it
+ $preview_count = $post['sticky'] ? $config['threads_preview_sticky'] :
+ $config['threads_preview'];
+
+ // Chomp the last few replies
+ $disp_replies = array_splice($replies, 0, $preview_count);
+ $disp_img_count = 0;
+ foreach ($disp_replies as $reply) {
+ if ($reply['files'] !== '')
+ ++$disp_img_count;
+
+ // Append the reply to the thread as it's being built
+ $thread->add(new Post($reply, $mod ? '?/' : $config['root'], $mod));
+ }
+
+ // Count the number of omitted image replies
+ $omitted_img_count = count(array_filter($replies, function($p) {
+ return $p['files'] !== '';
+ }));
+
+ // Set the corresponding omitted numbers on the thread
+ if (!empty($replies)) {
+ $thread->omitted = count($replies);
+ $thread->omitted_images = $omitted_img_count;
+ }
+
+ // Board name and link
+ $html = '<h2><a href="' . $config['root'] . $post['board'] . '/">/' .
+ $post['board'] . '/</a></h2>';
+ // The thread itself
+ $html .= $thread->build(true);
+
+ return $html;
+ }
+
+ /**
+ * Query the required information and generate the HTML
+ */
+ public function build($mod = false) {
+ if (!isset($this->settings)) {
+ error('Theme is not configured properly.');
+ }
+
+ global $config;
+
+ $html = '';
+ $overflow = array();
+
+ // Fetch threads from all boards and chomp the first 'n' posts, depending
+ // on the setting
+ $threads = $this->shuffleThreads($this->fetchThreads());
+ $total_count = count($threads);
+ // Top threads displayed on load
+ $top_threads = array_splice($threads, 0, $this->settings['thread_limit']);
+ // Number of processed threads by board
+ $counts = array();
+
+ // Output threads up to the specified limit
+ foreach ($top_threads as $post) {
+ if (array_key_exists($post['board'], $counts)) {
+ ++$counts[$post['board']];
+ } else {
+ $counts[$post['board']] = 1;
+ }
+
+ $html .= $this->buildOne($post, $mod);
+ }
+
+ foreach ($threads as $post) {
+ if (array_key_exists($post['board'], $counts)) {
+ ++$counts[$post['board']];
+ } else {
+ $counts[$post['board']] = 1;
+ }
+
+ $page = 'index';
+ $board_page = floor($counts[$post['board']] / $config['threads_per_page']);
+ if ($board_page > 0) {
+ $page = $board_page + 1;
+ }
+ $overflow[] = array(
+ 'id' => $post['id'],
+ 'board' => $post['board'],
+ 'page' => $page . '.html'
+ );
+ }
+
+ $html .= '<script>var ukko_overflow = ' . json_encode($overflow) . '</script>';
+ $html .= '<script type="text/javascript" src="/'.$this->settings['uri'].'/semirand.js"></script>';
+
+ return Element('index.html', array(
+ 'config' => $config,
+ 'board' => array(
+ 'url' => $this->settings['uri'],
+ 'title' => $this->settings['title'],
+ 'subtitle' => str_replace('%s', $this->settings['thread_limit'],
+ strval(min($this->settings['subtitle'], $total_count))),
+ ),
+ 'no_post_form' => true,
+ 'body' => $html,
+ 'mod' => $mod,
+ 'boardlist' => createBoardlist($mod),
+ ));
+ }
+
+ };
+
+ if (!function_exists('array_column')) {
+ /**
+ * Pick out values from subarrays by given key
+ */
+ function array_column($array, $key) {
+ $result = [];
+ foreach ($array as $val) {
+ $result[] = $val[$key];
+ }
+ return $result;
+ }
+ }
+