diff options
| author | Matěj Grabovský <mgrabovsky@yahoo.com> | 2015-06-02 15:28:06 +0200 |
|---|---|---|
| committer | Matěj Grabovský <mgrabovsky@yahoo.com> | 2015-07-03 16:13:16 +0200 |
| commit | 7c6eef81c55d6f5ccfa6ebba759afcec7b79115d (patch) | |
| tree | 8c5dbfee3d267700ff5a3c53a486eee607368c7d /templates/themes/semirand/theme.php | |
| parent | b25b443e55cf20420231e82e5ffdaeff6db595d5 (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.php | 254 |
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; + } + } + |
