From fe66c51a19920378885fb790d341cc50c933b046 Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 10 Mar 2015 13:13:18 +0100 Subject: ... (minor fix for locales) --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 2a398ae7..f1fb07d7 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -358,7 +358,7 @@ function rebuildThemes($action, $boardname = false) { // Reload the locale if ($config['locale'] != $current_locale) { $current_locale = $config['locale']; - init_locale($config['locale'], $error); + init_locale($config['locale']); } if (PHP_SAPI === 'cli') { -- cgit v1.3 From d2de4419bddbb7b0e09ced1f01d3ccb45aecc50f Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 14 Jan 2016 21:05:24 +0100 Subject: Added: config option to hide email in post. (prevent emailfag but let the possibility to sage and noko) --- inc/config.php | 3 +++ templates/post/name.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/inc/config.php b/inc/config.php index 69b4bd3a..d5bdbfe5 100644 --- a/inc/config.php +++ b/inc/config.php @@ -550,6 +550,9 @@ // When true, the sage won't be displayed $config['hide_sage'] = false; + // Don't display user's email when it's not "sage" + $config['hide_email'] = false; + // Attach country flags to posts. $config['country_flags'] = false; diff --git a/templates/post/name.html b/templates/post/name.html index 13c676c7..e7f85f8b 100644 --- a/templates/post/name.html +++ b/templates/post/name.html @@ -1,5 +1,5 @@ {% set capcode = post.capcode|capcode %} -{% if post.email|length > 0 and (config.hide_sage!=true or post.email!='sage') %} +{% if post.email|length > 0 and (config.hide_sage!=true or post.email!='sage') and config.hide_email!=true %} {# start email #} {% endif %} -- cgit v1.3 From 028fd3df15b2e2edc96c5b84156b0b039ea16562 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 14 Jan 2016 20:16:51 +0100 Subject: Added: RSS theme --- templates/themes/rss/info.php | 61 ++++++++++++++++++++ templates/themes/rss/rss.xml | 28 ++++++++++ templates/themes/rss/theme.php | 124 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100755 templates/themes/rss/info.php create mode 100644 templates/themes/rss/rss.xml create mode 100755 templates/themes/rss/theme.php diff --git a/templates/themes/rss/info.php b/templates/themes/rss/info.php new file mode 100755 index 00000000..14b39eb9 --- /dev/null +++ b/templates/themes/rss/info.php @@ -0,0 +1,61 @@ + 'Title', + 'name' => 'title', + 'type' => 'text', + 'default' => 'Recent Posts RSS' + ); + + $theme['config'][] = Array( + 'title' => 'Excluded boards', + 'name' => 'exclude', + 'type' => 'text', + 'comment' => '(space seperated)' + ); + + $theme['config'][] = Array( + 'title' => '# of recent posts', + 'name' => 'limit_posts', + 'type' => 'text', + 'default' => '30', + 'comment' => '(maximum posts to display)' + ); + + $theme['config'][] = Array( + 'title' => 'XML file', + 'name' => 'xml', + 'type' => 'text', + 'default' => 'recent.xml', + 'comment' => '(eg. "recent.xml")' + ); + + $theme['config'][] = Array( + 'title' => 'Base URL', + 'name' => 'base_url', + 'type' => 'text', + 'default' => 'http://test.com', + 'comment' => '(eg. "http://test.com")' + ); + + // Unique function name for building everything + $theme['build_function'] = 'rss_recentposts_build'; + $theme['install_callback'] = 'rss_recentposts_install'; + + if (!function_exists('rss_recentposts_install')) { + function rss_recentposts_install($settings) { + if (!is_numeric($settings['limit_posts']) || $settings['limit_posts'] < 0) + return Array(false, '' . utf8tohtml($settings['limit_posts']) . ' is not a non-negative integer.'); + } + } + diff --git a/templates/themes/rss/rss.xml b/templates/themes/rss/rss.xml new file mode 100644 index 00000000..30fdb473 --- /dev/null +++ b/templates/themes/rss/rss.xml @@ -0,0 +1,28 @@ +{% filter remove_whitespace %} + + + + + + + {{ settings.title }} + Tinyboard {{ config.version }} + {{ settings.base_url }}{{ config.root }}{{ settings.html }} + {% for post in recent_posts %} + + {{ post.board_name }} + + + {{ post.snippet }} + + ]]> + + {{ settings.base_url }}{{ post.link }} + {{ settings.base_url }}{{ post.link }} + + {% endfor %} + + +{% endfilter %} diff --git a/templates/themes/rss/theme.php b/templates/themes/rss/theme.php new file mode 100755 index 00000000..33c1b210 --- /dev/null +++ b/templates/themes/rss/theme.php @@ -0,0 +1,124 @@ +build($action, $settings); + } + + // Wrap functions in a class so they don't interfere with normal Tinyboard operations + class RSSRecentPosts { + public function build($action, $settings) { + global $config, $_theme; + + /*if ($action == 'all') { + copy('templates/themes/recent/' . $settings['basecss'], $config['dir']['home'] . $settings['css']); + }*/ + + $this->excluded = explode(' ', $settings['exclude']); + + if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') + file_write($config['dir']['home'] . $settings['xml'], $this->homepage($settings)); + } + + // Build news page + public function homepage($settings) { + global $config, $board; + + //$recent_images = Array(); + $recent_posts = Array(); + //$stats = Array(); + + $boards = listBoards(); + + /*$query = ''; + foreach ($boards as &$_board) { + if (in_array($_board['uri'], $this->excluded)) + continue; + $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` WHERE `file` IS NOT NULL AND `file` != 'deleted' AND `thumb` != 'spoiler' UNION ALL ", $_board['uri'], $_board['uri']); + } + $query = preg_replace('/UNION ALL $/', 'ORDER BY `time` DESC LIMIT ' . (int)$settings['limit_images'], $query); + $query = query($query) or error(db_error()); + + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + openBoard($post['board']); + + // board settings won't be available in the template file, so generate links now + $post['link'] = $config['root'] . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], ($post['thread'] ? $post['thread'] : $post['id'])) . '#' . $post['id']; + $post['src'] = $config['uri_thumb'] . $post['thumb']; + + //$recent_images[] = $post; + }*/ + + + $query = ''; + foreach ($boards as &$_board) { + if (in_array($_board['uri'], $this->excluded)) + continue; + $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` UNION ALL ", $_board['uri'], $_board['uri']); + } + $query = preg_replace('/UNION ALL $/', 'ORDER BY `time` DESC LIMIT ' . (int)$settings['limit_posts'], $query); + $query = query($query) or error(db_error()); + + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + openBoard($post['board']); + + $post['link'] = $config['root'] . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], ($post['thread'] ? $post['thread'] : $post['id'])) . '#' . $post['id']; + $post['snippet'] = pm_snippet($post['body'], 30); + $post['board_name'] = $board['name']; + + $recent_posts[] = $post; + } + + // Total posts + /*$query = 'SELECT SUM(`top`) FROM ('; + foreach ($boards as &$_board) { + if (in_array($_board['uri'], $this->excluded)) + continue; + $query .= sprintf("SELECT MAX(`id`) AS `top` FROM ``posts_%s`` UNION ALL ", $_board['uri']); + } + $query = preg_replace('/UNION ALL $/', ') AS `posts_all`', $query); + $query = query($query) or error(db_error());*/ + //$stats['total_posts'] = number_format($query->fetchColumn()); + + // Unique IPs + /*$query = 'SELECT COUNT(DISTINCT(`ip`)) FROM ('; + foreach ($boards as &$_board) { + if (in_array($_board['uri'], $this->excluded)) + continue; + $query .= sprintf("SELECT `ip` FROM ``posts_%s`` UNION ALL ", $_board['uri']); + } + $query = preg_replace('/UNION ALL $/', ') AS `posts_all`', $query); + $query = query($query) or error(db_error()); + //$stats['unique_posters'] = number_format($query->fetchColumn());*/ + + // Active content + /*$query = 'SELECT SUM(`filesize`) FROM ('; + foreach ($boards as &$_board) { + if (in_array($_board['uri'], $this->excluded)) + continue; + $query .= sprintf("SELECT `filesize` FROM ``posts_%s`` UNION ALL ", $_board['uri']); + } + $query = preg_replace('/UNION ALL $/', ') AS `posts_all`', $query); + $query = query($query) or error(db_error()); + //$stats['active_content'] = $query->fetchColumn();*/ + + return Element('themes/rss/rss.xml', Array( + 'settings' => $settings, + 'config' => $config, + //'boardlist' => createBoardlist(), + //'recent_images' => $recent_images, + 'recent_posts' => $recent_posts, + //'stats' => $stats + )); + } + }; + +?> -- cgit v1.3 From caaf741691c9d44a9a186d6ce924fecb04f4f347 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 22 Apr 2016 05:35:43 +0200 Subject: [SECURITY] keep up with modern password hashing standards --- inc/config.php | 15 ++++++++++++ inc/mod/auth.php | 71 ++++++++++++++++++++++++++++++++++++++++++++++--------- inc/mod/pages.php | 11 ++++----- install.php | 7 +++++- install.sql | 4 ++-- 5 files changed, 87 insertions(+), 21 deletions(-) diff --git a/inc/config.php b/inc/config.php index d5bdbfe5..c0e967af 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1672,3 +1672,18 @@ ''. ''. ''; + + // Password hashing function + // + // $5$ <- SHA256 + // $6$ <- SHA512 + // + // 25000 rounds make for ~0.05s on my 2015 Core i3 computer. + // + // https://secure.php.net/manual/en/function.crypt.php + $config['password_crypt'] = '$6$rounds=25000$'; + + // Password hashing method version + // If set to 0, it won't upgrade hashes using old password encryption schema, only create new. + // You can set it to a higher value, to further migrate to other password hashing function. + $config['password_crypt_version'] = 1; diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 01ed5b68..fa1a0f4f 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -18,7 +18,18 @@ function mkhash($username, $password, $salt = false) { } // generate hash (method is not important as long as it's strong) - $hash = substr(base64_encode(md5($username . $config['cookies']['salt'] . sha1($username . $password . $salt . ($config['mod']['lock_ip'] ? $_SERVER['REMOTE_ADDR'] : ''), true), true)), 0, 20); + $hash = substr( + base64_encode( + md5( + $username . $config['cookies']['salt'] . sha1( + $username . $password . $salt . ( + $config['mod']['lock_ip'] ? $_SERVER['REMOTE_ADDR'] : '' + ), true + ) . sha1($config['password_crypt_version']) // Log out users being logged in with older password encryption schema + , true + ) + ), 0, 20 + ); if (isset($generated_salt)) return array($hash, $salt); @@ -26,25 +37,63 @@ function mkhash($username, $password, $salt = false) { return $hash; } -function generate_salt() { - mt_srand(microtime(true) * 100000 + memory_get_usage(true)); - return md5(uniqid(mt_rand(), true)); +function crypt_password_old($password) { + $salt = generate_salt(); + $password = hash('sha256', $salt . sha1($password)); + return array($salt, $password); } -function login($username, $password, $makehash=true) { - global $mod; - - // SHA1 password - if ($makehash) { - $password = sha1($password); +function crypt_password($password) { + global $config; + // `salt` database field is reused as a version value. We don't want it to be 0. + $version = $config['password_crypt_version'] ? $config['password_crypt_version'] : 1; + $new_salt = generate_salt(); + $password = crypt($password, $config['password_crypt'] . $new_salt . "$"); + return array($version, $password); +} + +function test_password($password, $salt, $test) { + global $config; + + // Version = 0 denotes an old password hashing schema. In the same column, the + // password hash was kept previously + $version = (strlen($salt) <= 8) ? (int) $salt : 0; + + if ($version == 0) { + $comp = hash('sha256', $salt . sha1($test)); } + else { + $comp = crypt($test, $password); + } + return array($version, hash_equals($password, $comp)); +} + +function generate_salt() { + // 128 bits of entropy + return strtr(base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)), '+', '.'); +} + +function login($username, $password) { + global $mod, $config; $query = prepare("SELECT `id`, `type`, `boards`, `password`, `salt` FROM ``mods`` WHERE `username` = :username"); $query->bindValue(':username', $username); $query->execute() or error(db_error($query)); if ($user = $query->fetch(PDO::FETCH_ASSOC)) { - if ($user['password'] === hash('sha256', $user['salt'] . $password)) { + list($version, $ok) = test_password($user['password'], $user['salt'], $password); + + if ($ok) { + if ($config['password_crypt_version'] > $version) { + // It's time to upgrade the password hashing method! + list ($user['salt'], $user['password']) = crypt_password($password); + $query = prepare("UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id"); + $query->bindValue(':password', $user['password']); + $query->bindValue(':salt', $user['salt']); + $query->bindValue(':id', $user['id']); + $query->execute() or error(db_error($query)); + } + return $mod = array( 'id' => $user['id'], 'type' => $user['type'], diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 2c679b20..a07de4c7 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1734,9 +1734,8 @@ function mod_user($uid) { } if ($_POST['password'] != '') { - $salt = generate_salt(); - $password = hash('sha256', $salt . sha1($_POST['password'])); - + list($salt, $password) = crypt_password($_POST['password']); + $query = prepare('UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id'); $query->bindValue(':id', $uid); $query->bindValue(':password', $password); @@ -1761,8 +1760,7 @@ function mod_user($uid) { if (hasPermission($config['mod']['change_password']) && $uid == $mod['id'] && isset($_POST['password'])) { if ($_POST['password'] != '') { - $salt = generate_salt(); - $password = hash('sha256', $salt . sha1($_POST['password'])); + list($salt, $password) = crypt_password($_POST['password']); $query = prepare('UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id'); $query->bindValue(':id', $uid); @@ -1834,8 +1832,7 @@ function mod_user_new() { if (!isset($config['mod']['groups'][$type]) || $type == DISABLED) error(sprintf($config['error']['invalidfield'], 'type')); - $salt = generate_salt(); - $password = hash('sha256', $salt . sha1($_POST['password'])); + list($salt, $password) = crypt_password($_POST['password']); $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, :type, :boards)'); $query->bindValue(':username', $_POST['username']); diff --git a/install.php b/install.php index 95caf8ec..5a2d724a 100644 --- a/install.php +++ b/install.php @@ -1,10 +1,12 @@ $config, @@ -551,6 +553,9 @@ if (file_exists($config['has_installed'])) { foreach ($boards as &$board) { query(sprintf('ALTER TABLE ``posts_%s`` ADD `slug` VARCHAR(255) DEFAULT NULL AFTER `embed`;', $board['uri'])) or error(db_error()); } + case '4.9.93': + query('ALTER TABLE ``mods`` CHANGE `password` `password` VARCHAR(255) NOT NULL;') or error(db_error()); + query('ALTER TABLE ``mods`` CHANGE `salt` `salt` VARCHAR(64) NOT NULL;') or error(db_error()); case false: // TODO: enhance Tinyboard -> vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); diff --git a/install.sql b/install.sql index 969107a2..fbf220c1 100644 --- a/install.sql +++ b/install.sql @@ -131,8 +131,8 @@ CREATE TABLE IF NOT EXISTS `modlogs` ( CREATE TABLE IF NOT EXISTS `mods` ( `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(30) NOT NULL, - `password` char(64) CHARACTER SET ascii NOT NULL COMMENT 'SHA256', - `salt` char(32) CHARACTER SET ascii NOT NULL, + `password` varchar(256) CHARACTER SET ascii NOT NULL COMMENT 'SHA256', + `salt` varchar(64) CHARACTER SET ascii NOT NULL, `type` smallint(2) NOT NULL, `boards` text CHARACTER SET utf8 NOT NULL, PRIMARY KEY (`id`), -- cgit v1.3 From 48726950d9e2e60d543c8e30ac2e719fb3b80822 Mon Sep 17 00:00:00 2001 From: 27chan <27chan@openmailbox.org> Date: Wed, 27 Apr 2016 14:25:51 -0300 Subject: Update Font Awesome Update Font Awesome to 4.6.1 --- stylesheets/font-awesome/css/font-awesome.css | 168 ++++++++++++++++++++- stylesheets/font-awesome/css/font-awesome.min.css | 4 +- stylesheets/font-awesome/fonts/FontAwesome.otf | Bin 106260 -> 123112 bytes .../font-awesome/fonts/fontawesome-webfont.eot | Bin 68875 -> 75220 bytes .../font-awesome/fonts/fontawesome-webfont.svg | 99 ++++++++---- .../font-awesome/fonts/fontawesome-webfont.ttf | Bin 138204 -> 150920 bytes .../font-awesome/fonts/fontawesome-webfont.woff | Bin 81284 -> 89076 bytes .../font-awesome/fonts/fontawesome-webfont.woff2 | Bin 64464 -> 70728 bytes stylesheets/font-awesome/less/font-awesome.less | 3 +- stylesheets/font-awesome/less/icons.less | 47 ++++++ stylesheets/font-awesome/less/mixins.less | 38 ++++- stylesheets/font-awesome/less/path.less | 2 +- stylesheets/font-awesome/less/screen-reader.less | 5 + stylesheets/font-awesome/less/variables.less | 51 ++++++- stylesheets/font-awesome/scss/_icons.scss | 47 ++++++ stylesheets/font-awesome/scss/_mixins.scss | 38 ++++- stylesheets/font-awesome/scss/_screen-reader.scss | 5 + stylesheets/font-awesome/scss/_variables.scss | 51 ++++++- stylesheets/font-awesome/scss/font-awesome.scss | 3 +- 19 files changed, 513 insertions(+), 48 deletions(-) create mode 100644 stylesheets/font-awesome/less/screen-reader.less create mode 100644 stylesheets/font-awesome/scss/_screen-reader.scss diff --git a/stylesheets/font-awesome/css/font-awesome.css b/stylesheets/font-awesome/css/font-awesome.css index 880eb825..bb0fe51a 100644 --- a/stylesheets/font-awesome/css/font-awesome.css +++ b/stylesheets/font-awesome/css/font-awesome.css @@ -1,13 +1,13 @@ /*! - * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.6.1 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; - src: url('../fonts/fontawesome-webfont.eot?v=4.4.0'); - src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg'); + src: url('../fonts/fontawesome-webfont.eot?v=4.6.1'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.1') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.6.1') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.6.1') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.6.1') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.6.1#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } @@ -118,31 +118,31 @@ } } .fa-rotate-90 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; -webkit-transform: scale(-1, 1); -ms-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; -webkit-transform: scale(1, -1); -ms-transform: scale(1, -1); transform: scale(1, -1); @@ -2024,3 +2024,155 @@ .fa-fonticons:before { content: "\f280"; } +.fa-reddit-alien:before { + content: "\f281"; +} +.fa-edge:before { + content: "\f282"; +} +.fa-credit-card-alt:before { + content: "\f283"; +} +.fa-codiepie:before { + content: "\f284"; +} +.fa-modx:before { + content: "\f285"; +} +.fa-fort-awesome:before { + content: "\f286"; +} +.fa-usb:before { + content: "\f287"; +} +.fa-product-hunt:before { + content: "\f288"; +} +.fa-mixcloud:before { + content: "\f289"; +} +.fa-scribd:before { + content: "\f28a"; +} +.fa-pause-circle:before { + content: "\f28b"; +} +.fa-pause-circle-o:before { + content: "\f28c"; +} +.fa-stop-circle:before { + content: "\f28d"; +} +.fa-stop-circle-o:before { + content: "\f28e"; +} +.fa-shopping-bag:before { + content: "\f290"; +} +.fa-shopping-basket:before { + content: "\f291"; +} +.fa-hashtag:before { + content: "\f292"; +} +.fa-bluetooth:before { + content: "\f293"; +} +.fa-bluetooth-b:before { + content: "\f294"; +} +.fa-percent:before { + content: "\f295"; +} +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/stylesheets/font-awesome/css/font-awesome.min.css b/stylesheets/font-awesome/css/font-awesome.min.css index ee4e9782..885b3840 100644 --- a/stylesheets/font-awesome/css/font-awesome.min.css +++ b/stylesheets/font-awesome/css/font-awesome.min.css @@ -1,4 +1,4 @@ /*! - * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.6.1 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.1');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.1') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.1') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.1') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.1') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.1#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/stylesheets/font-awesome/fonts/FontAwesome.otf b/stylesheets/font-awesome/fonts/FontAwesome.otf index 681bdd4d..59853bcd 100644 Binary files a/stylesheets/font-awesome/fonts/FontAwesome.otf and b/stylesheets/font-awesome/fonts/FontAwesome.otf differ diff --git a/stylesheets/font-awesome/fonts/fontawesome-webfont.eot b/stylesheets/font-awesome/fonts/fontawesome-webfont.eot index a30335d7..96f92f9b 100644 Binary files a/stylesheets/font-awesome/fonts/fontawesome-webfont.eot and b/stylesheets/font-awesome/fonts/fontawesome-webfont.eot differ diff --git a/stylesheets/font-awesome/fonts/fontawesome-webfont.svg b/stylesheets/font-awesome/fonts/fontawesome-webfont.svg index 6fd19abc..5a5f0ecd 100644 --- a/stylesheets/font-awesome/fonts/fontawesome-webfont.svg +++ b/stylesheets/font-awesome/fonts/fontawesome-webfont.svg @@ -1,6 +1,6 @@ - + @@ -169,7 +169,7 @@ - + @@ -178,7 +178,7 @@ - + @@ -219,8 +219,8 @@ - - + + @@ -362,7 +362,7 @@ - + @@ -410,7 +410,7 @@ - + @@ -454,7 +454,7 @@ - + @@ -484,7 +484,7 @@ - + @@ -555,7 +555,7 @@ - + @@ -600,11 +600,11 @@ - - + + - + @@ -621,20 +621,65 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf b/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf index d7994e13..86784df9 100644 Binary files a/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf and b/stylesheets/font-awesome/fonts/fontawesome-webfont.ttf differ diff --git a/stylesheets/font-awesome/fonts/fontawesome-webfont.woff b/stylesheets/font-awesome/fonts/fontawesome-webfont.woff index 6fd4ede0..c7faa19c 100644 Binary files a/stylesheets/font-awesome/fonts/fontawesome-webfont.woff and b/stylesheets/font-awesome/fonts/fontawesome-webfont.woff differ diff --git a/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2 b/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2 index 5560193c..cab8571d 100644 Binary files a/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2 and b/stylesheets/font-awesome/fonts/fontawesome-webfont.woff2 differ diff --git a/stylesheets/font-awesome/less/font-awesome.less b/stylesheets/font-awesome/less/font-awesome.less index e3f89c8f..76709600 100644 --- a/stylesheets/font-awesome/less/font-awesome.less +++ b/stylesheets/font-awesome/less/font-awesome.less @@ -1,5 +1,5 @@ /*! - * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.6.1 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ @@ -15,3 +15,4 @@ @import "rotated-flipped.less"; @import "stacked.less"; @import "icons.less"; +@import "screen-reader.less"; diff --git a/stylesheets/font-awesome/less/icons.less b/stylesheets/font-awesome/less/icons.less index 6ebe9669..c5e64309 100644 --- a/stylesheets/font-awesome/less/icons.less +++ b/stylesheets/font-awesome/less/icons.less @@ -675,3 +675,50 @@ .@{fa-css-prefix}-vimeo:before { content: @fa-var-vimeo; } .@{fa-css-prefix}-black-tie:before { content: @fa-var-black-tie; } .@{fa-css-prefix}-fonticons:before { content: @fa-var-fonticons; } +.@{fa-css-prefix}-reddit-alien:before { content: @fa-var-reddit-alien; } +.@{fa-css-prefix}-edge:before { content: @fa-var-edge; } +.@{fa-css-prefix}-credit-card-alt:before { content: @fa-var-credit-card-alt; } +.@{fa-css-prefix}-codiepie:before { content: @fa-var-codiepie; } +.@{fa-css-prefix}-modx:before { content: @fa-var-modx; } +.@{fa-css-prefix}-fort-awesome:before { content: @fa-var-fort-awesome; } +.@{fa-css-prefix}-usb:before { content: @fa-var-usb; } +.@{fa-css-prefix}-product-hunt:before { content: @fa-var-product-hunt; } +.@{fa-css-prefix}-mixcloud:before { content: @fa-var-mixcloud; } +.@{fa-css-prefix}-scribd:before { content: @fa-var-scribd; } +.@{fa-css-prefix}-pause-circle:before { content: @fa-var-pause-circle; } +.@{fa-css-prefix}-pause-circle-o:before { content: @fa-var-pause-circle-o; } +.@{fa-css-prefix}-stop-circle:before { content: @fa-var-stop-circle; } +.@{fa-css-prefix}-stop-circle-o:before { content: @fa-var-stop-circle-o; } +.@{fa-css-prefix}-shopping-bag:before { content: @fa-var-shopping-bag; } +.@{fa-css-prefix}-shopping-basket:before { content: @fa-var-shopping-basket; } +.@{fa-css-prefix}-hashtag:before { content: @fa-var-hashtag; } +.@{fa-css-prefix}-bluetooth:before { content: @fa-var-bluetooth; } +.@{fa-css-prefix}-bluetooth-b:before { content: @fa-var-bluetooth-b; } +.@{fa-css-prefix}-percent:before { content: @fa-var-percent; } +.@{fa-css-prefix}-gitlab:before { content: @fa-var-gitlab; } +.@{fa-css-prefix}-wpbeginner:before { content: @fa-var-wpbeginner; } +.@{fa-css-prefix}-wpforms:before { content: @fa-var-wpforms; } +.@{fa-css-prefix}-envira:before { content: @fa-var-envira; } +.@{fa-css-prefix}-universal-access:before { content: @fa-var-universal-access; } +.@{fa-css-prefix}-wheelchair-alt:before { content: @fa-var-wheelchair-alt; } +.@{fa-css-prefix}-question-circle-o:before { content: @fa-var-question-circle-o; } +.@{fa-css-prefix}-blind:before { content: @fa-var-blind; } +.@{fa-css-prefix}-audio-description:before { content: @fa-var-audio-description; } +.@{fa-css-prefix}-volume-control-phone:before { content: @fa-var-volume-control-phone; } +.@{fa-css-prefix}-braille:before { content: @fa-var-braille; } +.@{fa-css-prefix}-assistive-listening-systems:before { content: @fa-var-assistive-listening-systems; } +.@{fa-css-prefix}-asl-interpreting:before, +.@{fa-css-prefix}-american-sign-language-interpreting:before { content: @fa-var-american-sign-language-interpreting; } +.@{fa-css-prefix}-deafness:before, +.@{fa-css-prefix}-hard-of-hearing:before, +.@{fa-css-prefix}-deaf:before { content: @fa-var-deaf; } +.@{fa-css-prefix}-glide:before { content: @fa-var-glide; } +.@{fa-css-prefix}-glide-g:before { content: @fa-var-glide-g; } +.@{fa-css-prefix}-signing:before, +.@{fa-css-prefix}-sign-language:before { content: @fa-var-sign-language; } +.@{fa-css-prefix}-low-vision:before { content: @fa-var-low-vision; } +.@{fa-css-prefix}-viadeo:before { content: @fa-var-viadeo; } +.@{fa-css-prefix}-viadeo-square:before { content: @fa-var-viadeo-square; } +.@{fa-css-prefix}-snapchat:before { content: @fa-var-snapchat; } +.@{fa-css-prefix}-snapchat-ghost:before { content: @fa-var-snapchat-ghost; } +.@{fa-css-prefix}-snapchat-square:before { content: @fa-var-snapchat-square; } diff --git a/stylesheets/font-awesome/less/mixins.less b/stylesheets/font-awesome/less/mixins.less index d5a43a14..beef231d 100644 --- a/stylesheets/font-awesome/less/mixins.less +++ b/stylesheets/font-awesome/less/mixins.less @@ -12,15 +12,49 @@ } .fa-icon-rotate(@degrees, @rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; -webkit-transform: rotate(@degrees); -ms-transform: rotate(@degrees); transform: rotate(@degrees); } .fa-icon-flip(@horiz, @vert, @rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; -webkit-transform: scale(@horiz, @vert); -ms-transform: scale(@horiz, @vert); transform: scale(@horiz, @vert); } + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +.sr-only() { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +.sr-only-focusable() { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/stylesheets/font-awesome/less/path.less b/stylesheets/font-awesome/less/path.less index 9211e665..835be41f 100644 --- a/stylesheets/font-awesome/less/path.less +++ b/stylesheets/font-awesome/less/path.less @@ -9,7 +9,7 @@ url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); -// src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts + // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts font-weight: normal; font-style: normal; } diff --git a/stylesheets/font-awesome/less/screen-reader.less b/stylesheets/font-awesome/less/screen-reader.less new file mode 100644 index 00000000..11c18819 --- /dev/null +++ b/stylesheets/font-awesome/less/screen-reader.less @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { .sr-only(); } +.sr-only-focusable { .sr-only-focusable(); } diff --git a/stylesheets/font-awesome/less/variables.less b/stylesheets/font-awesome/less/variables.less index 00418e75..8118e8f7 100644 --- a/stylesheets/font-awesome/less/variables.less +++ b/stylesheets/font-awesome/less/variables.less @@ -4,9 +4,9 @@ @fa-font-path: "../fonts"; @fa-font-size-base: 14px; @fa-line-height-base: 1; -//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.4.0/fonts"; // for referencing Bootstrap CDN font files directly +//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.6.1/fonts"; // for referencing Bootstrap CDN font files directly @fa-css-prefix: fa; -@fa-version: "4.4.0"; +@fa-version: "4.6.1"; @fa-border-color: #eee; @fa-inverse: #fff; @fa-li-width: (30em / 14); @@ -20,6 +20,7 @@ @fa-var-align-right: "\f038"; @fa-var-amazon: "\f270"; @fa-var-ambulance: "\f0f9"; +@fa-var-american-sign-language-interpreting: "\f2a3"; @fa-var-anchor: "\f13d"; @fa-var-android: "\f17b"; @fa-var-angellist: "\f209"; @@ -50,8 +51,11 @@ @fa-var-arrows-alt: "\f0b2"; @fa-var-arrows-h: "\f07e"; @fa-var-arrows-v: "\f07d"; +@fa-var-asl-interpreting: "\f2a3"; +@fa-var-assistive-listening-systems: "\f2a2"; @fa-var-asterisk: "\f069"; @fa-var-at: "\f1fa"; +@fa-var-audio-description: "\f29e"; @fa-var-automobile: "\f1b9"; @fa-var-backward: "\f04a"; @fa-var-balance-scale: "\f24e"; @@ -86,12 +90,16 @@ @fa-var-bitbucket-square: "\f172"; @fa-var-bitcoin: "\f15a"; @fa-var-black-tie: "\f27e"; +@fa-var-blind: "\f29d"; +@fa-var-bluetooth: "\f293"; +@fa-var-bluetooth-b: "\f294"; @fa-var-bold: "\f032"; @fa-var-bolt: "\f0e7"; @fa-var-bomb: "\f1e2"; @fa-var-book: "\f02d"; @fa-var-bookmark: "\f02e"; @fa-var-bookmark-o: "\f097"; +@fa-var-braille: "\f2a1"; @fa-var-briefcase: "\f0b1"; @fa-var-btc: "\f15a"; @fa-var-bug: "\f188"; @@ -164,6 +172,7 @@ @fa-var-code: "\f121"; @fa-var-code-fork: "\f126"; @fa-var-codepen: "\f1cb"; +@fa-var-codiepie: "\f284"; @fa-var-coffee: "\f0f4"; @fa-var-cog: "\f013"; @fa-var-cogs: "\f085"; @@ -182,6 +191,7 @@ @fa-var-copyright: "\f1f9"; @fa-var-creative-commons: "\f25e"; @fa-var-credit-card: "\f09d"; +@fa-var-credit-card-alt: "\f283"; @fa-var-crop: "\f125"; @fa-var-crosshairs: "\f05b"; @fa-var-css3: "\f13c"; @@ -192,6 +202,8 @@ @fa-var-dashboard: "\f0e4"; @fa-var-dashcube: "\f210"; @fa-var-database: "\f1c0"; +@fa-var-deaf: "\f2a4"; +@fa-var-deafness: "\f2a4"; @fa-var-dedent: "\f03b"; @fa-var-delicious: "\f1a5"; @fa-var-desktop: "\f108"; @@ -204,6 +216,7 @@ @fa-var-dribbble: "\f17d"; @fa-var-dropbox: "\f16b"; @fa-var-drupal: "\f1a9"; +@fa-var-edge: "\f282"; @fa-var-edit: "\f044"; @fa-var-eject: "\f052"; @fa-var-ellipsis-h: "\f141"; @@ -212,6 +225,7 @@ @fa-var-envelope: "\f0e0"; @fa-var-envelope-o: "\f003"; @fa-var-envelope-square: "\f199"; +@fa-var-envira: "\f299"; @fa-var-eraser: "\f12d"; @fa-var-eur: "\f153"; @fa-var-euro: "\f153"; @@ -273,6 +287,7 @@ @fa-var-folder-open-o: "\f115"; @fa-var-font: "\f031"; @fa-var-fonticons: "\f280"; +@fa-var-fort-awesome: "\f286"; @fa-var-forumbee: "\f211"; @fa-var-forward: "\f04e"; @fa-var-foursquare: "\f180"; @@ -294,8 +309,11 @@ @fa-var-github: "\f09b"; @fa-var-github-alt: "\f113"; @fa-var-github-square: "\f092"; +@fa-var-gitlab: "\f296"; @fa-var-gittip: "\f184"; @fa-var-glass: "\f000"; +@fa-var-glide: "\f2a5"; +@fa-var-glide-g: "\f2a6"; @fa-var-globe: "\f0ac"; @fa-var-google: "\f1a0"; @fa-var-google-plus: "\f0d5"; @@ -319,6 +337,8 @@ @fa-var-hand-scissors-o: "\f257"; @fa-var-hand-spock-o: "\f259"; @fa-var-hand-stop-o: "\f256"; +@fa-var-hard-of-hearing: "\f2a4"; +@fa-var-hashtag: "\f292"; @fa-var-hdd-o: "\f0a0"; @fa-var-header: "\f1dc"; @fa-var-headphones: "\f025"; @@ -390,6 +410,7 @@ @fa-var-long-arrow-left: "\f177"; @fa-var-long-arrow-right: "\f178"; @fa-var-long-arrow-up: "\f176"; +@fa-var-low-vision: "\f2a8"; @fa-var-magic: "\f0d0"; @fa-var-magnet: "\f076"; @fa-var-mail-forward: "\f064"; @@ -418,8 +439,10 @@ @fa-var-minus-circle: "\f056"; @fa-var-minus-square: "\f146"; @fa-var-minus-square-o: "\f147"; +@fa-var-mixcloud: "\f289"; @fa-var-mobile: "\f10b"; @fa-var-mobile-phone: "\f10b"; +@fa-var-modx: "\f285"; @fa-var-money: "\f0d6"; @fa-var-moon-o: "\f186"; @fa-var-mortar-board: "\f19d"; @@ -446,11 +469,14 @@ @fa-var-paragraph: "\f1dd"; @fa-var-paste: "\f0ea"; @fa-var-pause: "\f04c"; +@fa-var-pause-circle: "\f28b"; +@fa-var-pause-circle-o: "\f28c"; @fa-var-paw: "\f1b0"; @fa-var-paypal: "\f1ed"; @fa-var-pencil: "\f040"; @fa-var-pencil-square: "\f14b"; @fa-var-pencil-square-o: "\f044"; +@fa-var-percent: "\f295"; @fa-var-phone: "\f095"; @fa-var-phone-square: "\f098"; @fa-var-photo: "\f03e"; @@ -472,11 +498,13 @@ @fa-var-plus-square-o: "\f196"; @fa-var-power-off: "\f011"; @fa-var-print: "\f02f"; +@fa-var-product-hunt: "\f288"; @fa-var-puzzle-piece: "\f12e"; @fa-var-qq: "\f1d6"; @fa-var-qrcode: "\f029"; @fa-var-question: "\f128"; @fa-var-question-circle: "\f059"; +@fa-var-question-circle-o: "\f29c"; @fa-var-quote-left: "\f10d"; @fa-var-quote-right: "\f10e"; @fa-var-ra: "\f1d0"; @@ -484,6 +512,7 @@ @fa-var-rebel: "\f1d0"; @fa-var-recycle: "\f1b8"; @fa-var-reddit: "\f1a1"; +@fa-var-reddit-alien: "\f281"; @fa-var-reddit-square: "\f1a2"; @fa-var-refresh: "\f021"; @fa-var-registered: "\f25d"; @@ -508,6 +537,7 @@ @fa-var-safari: "\f267"; @fa-var-save: "\f0c7"; @fa-var-scissors: "\f0c4"; +@fa-var-scribd: "\f28a"; @fa-var-search: "\f002"; @fa-var-search-minus: "\f010"; @fa-var-search-plus: "\f00e"; @@ -525,10 +555,14 @@ @fa-var-shield: "\f132"; @fa-var-ship: "\f21a"; @fa-var-shirtsinbulk: "\f214"; +@fa-var-shopping-bag: "\f290"; +@fa-var-shopping-basket: "\f291"; @fa-var-shopping-cart: "\f07a"; @fa-var-sign-in: "\f090"; +@fa-var-sign-language: "\f2a7"; @fa-var-sign-out: "\f08b"; @fa-var-signal: "\f012"; +@fa-var-signing: "\f2a7"; @fa-var-simplybuilt: "\f215"; @fa-var-sitemap: "\f0e8"; @fa-var-skyatlas: "\f216"; @@ -537,6 +571,9 @@ @fa-var-sliders: "\f1de"; @fa-var-slideshare: "\f1e7"; @fa-var-smile-o: "\f118"; +@fa-var-snapchat: "\f2ab"; +@fa-var-snapchat-ghost: "\f2ac"; +@fa-var-snapchat-square: "\f2ad"; @fa-var-soccer-ball-o: "\f1e3"; @fa-var-sort: "\f0dc"; @fa-var-sort-alpha-asc: "\f15d"; @@ -572,6 +609,8 @@ @fa-var-sticky-note: "\f249"; @fa-var-sticky-note-o: "\f24a"; @fa-var-stop: "\f04d"; +@fa-var-stop-circle: "\f28d"; +@fa-var-stop-circle-o: "\f28e"; @fa-var-street-view: "\f21d"; @fa-var-strikethrough: "\f0cc"; @fa-var-stumbleupon: "\f1a4"; @@ -636,12 +675,14 @@ @fa-var-umbrella: "\f0e9"; @fa-var-underline: "\f0cd"; @fa-var-undo: "\f0e2"; +@fa-var-universal-access: "\f29a"; @fa-var-university: "\f19c"; @fa-var-unlink: "\f127"; @fa-var-unlock: "\f09c"; @fa-var-unlock-alt: "\f13e"; @fa-var-unsorted: "\f0dc"; @fa-var-upload: "\f093"; +@fa-var-usb: "\f287"; @fa-var-usd: "\f155"; @fa-var-user: "\f007"; @fa-var-user-md: "\f0f0"; @@ -653,11 +694,14 @@ @fa-var-venus-double: "\f226"; @fa-var-venus-mars: "\f228"; @fa-var-viacoin: "\f237"; +@fa-var-viadeo: "\f2a9"; +@fa-var-viadeo-square: "\f2aa"; @fa-var-video-camera: "\f03d"; @fa-var-vimeo: "\f27d"; @fa-var-vimeo-square: "\f194"; @fa-var-vine: "\f1ca"; @fa-var-vk: "\f189"; +@fa-var-volume-control-phone: "\f2a0"; @fa-var-volume-down: "\f027"; @fa-var-volume-off: "\f026"; @fa-var-volume-up: "\f028"; @@ -667,11 +711,14 @@ @fa-var-weixin: "\f1d7"; @fa-var-whatsapp: "\f232"; @fa-var-wheelchair: "\f193"; +@fa-var-wheelchair-alt: "\f29b"; @fa-var-wifi: "\f1eb"; @fa-var-wikipedia-w: "\f266"; @fa-var-windows: "\f17a"; @fa-var-won: "\f159"; @fa-var-wordpress: "\f19a"; +@fa-var-wpbeginner: "\f297"; +@fa-var-wpforms: "\f298"; @fa-var-wrench: "\f0ad"; @fa-var-xing: "\f168"; @fa-var-xing-square: "\f169"; diff --git a/stylesheets/font-awesome/scss/_icons.scss b/stylesheets/font-awesome/scss/_icons.scss index 62d97677..b64017ae 100644 --- a/stylesheets/font-awesome/scss/_icons.scss +++ b/stylesheets/font-awesome/scss/_icons.scss @@ -675,3 +675,50 @@ .#{$fa-css-prefix}-vimeo:before { content: $fa-var-vimeo; } .#{$fa-css-prefix}-black-tie:before { content: $fa-var-black-tie; } .#{$fa-css-prefix}-fonticons:before { content: $fa-var-fonticons; } +.#{$fa-css-prefix}-reddit-alien:before { content: $fa-var-reddit-alien; } +.#{$fa-css-prefix}-edge:before { content: $fa-var-edge; } +.#{$fa-css-prefix}-credit-card-alt:before { content: $fa-var-credit-card-alt; } +.#{$fa-css-prefix}-codiepie:before { content: $fa-var-codiepie; } +.#{$fa-css-prefix}-modx:before { content: $fa-var-modx; } +.#{$fa-css-prefix}-fort-awesome:before { content: $fa-var-fort-awesome; } +.#{$fa-css-prefix}-usb:before { content: $fa-var-usb; } +.#{$fa-css-prefix}-product-hunt:before { content: $fa-var-product-hunt; } +.#{$fa-css-prefix}-mixcloud:before { content: $fa-var-mixcloud; } +.#{$fa-css-prefix}-scribd:before { content: $fa-var-scribd; } +.#{$fa-css-prefix}-pause-circle:before { content: $fa-var-pause-circle; } +.#{$fa-css-prefix}-pause-circle-o:before { content: $fa-var-pause-circle-o; } +.#{$fa-css-prefix}-stop-circle:before { content: $fa-var-stop-circle; } +.#{$fa-css-prefix}-stop-circle-o:before { content: $fa-var-stop-circle-o; } +.#{$fa-css-prefix}-shopping-bag:before { content: $fa-var-shopping-bag; } +.#{$fa-css-prefix}-shopping-basket:before { content: $fa-var-shopping-basket; } +.#{$fa-css-prefix}-hashtag:before { content: $fa-var-hashtag; } +.#{$fa-css-prefix}-bluetooth:before { content: $fa-var-bluetooth; } +.#{$fa-css-prefix}-bluetooth-b:before { content: $fa-var-bluetooth-b; } +.#{$fa-css-prefix}-percent:before { content: $fa-var-percent; } +.#{$fa-css-prefix}-gitlab:before { content: $fa-var-gitlab; } +.#{$fa-css-prefix}-wpbeginner:before { content: $fa-var-wpbeginner; } +.#{$fa-css-prefix}-wpforms:before { content: $fa-var-wpforms; } +.#{$fa-css-prefix}-envira:before { content: $fa-var-envira; } +.#{$fa-css-prefix}-universal-access:before { content: $fa-var-universal-access; } +.#{$fa-css-prefix}-wheelchair-alt:before { content: $fa-var-wheelchair-alt; } +.#{$fa-css-prefix}-question-circle-o:before { content: $fa-var-question-circle-o; } +.#{$fa-css-prefix}-blind:before { content: $fa-var-blind; } +.#{$fa-css-prefix}-audio-description:before { content: $fa-var-audio-description; } +.#{$fa-css-prefix}-volume-control-phone:before { content: $fa-var-volume-control-phone; } +.#{$fa-css-prefix}-braille:before { content: $fa-var-braille; } +.#{$fa-css-prefix}-assistive-listening-systems:before { content: $fa-var-assistive-listening-systems; } +.#{$fa-css-prefix}-asl-interpreting:before, +.#{$fa-css-prefix}-american-sign-language-interpreting:before { content: $fa-var-american-sign-language-interpreting; } +.#{$fa-css-prefix}-deafness:before, +.#{$fa-css-prefix}-hard-of-hearing:before, +.#{$fa-css-prefix}-deaf:before { content: $fa-var-deaf; } +.#{$fa-css-prefix}-glide:before { content: $fa-var-glide; } +.#{$fa-css-prefix}-glide-g:before { content: $fa-var-glide-g; } +.#{$fa-css-prefix}-signing:before, +.#{$fa-css-prefix}-sign-language:before { content: $fa-var-sign-language; } +.#{$fa-css-prefix}-low-vision:before { content: $fa-var-low-vision; } +.#{$fa-css-prefix}-viadeo:before { content: $fa-var-viadeo; } +.#{$fa-css-prefix}-viadeo-square:before { content: $fa-var-viadeo-square; } +.#{$fa-css-prefix}-snapchat:before { content: $fa-var-snapchat; } +.#{$fa-css-prefix}-snapchat-ghost:before { content: $fa-var-snapchat-ghost; } +.#{$fa-css-prefix}-snapchat-square:before { content: $fa-var-snapchat-square; } diff --git a/stylesheets/font-awesome/scss/_mixins.scss b/stylesheets/font-awesome/scss/_mixins.scss index f96719b6..c3bbd574 100644 --- a/stylesheets/font-awesome/scss/_mixins.scss +++ b/stylesheets/font-awesome/scss/_mixins.scss @@ -12,15 +12,49 @@ } @mixin fa-icon-rotate($degrees, $rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; -webkit-transform: rotate($degrees); -ms-transform: rotate($degrees); transform: rotate($degrees); } @mixin fa-icon-flip($horiz, $vert, $rotation) { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; -webkit-transform: scale($horiz, $vert); -ms-transform: scale($horiz, $vert); transform: scale($horiz, $vert); } + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +@mixin sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +@mixin sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/stylesheets/font-awesome/scss/_screen-reader.scss b/stylesheets/font-awesome/scss/_screen-reader.scss new file mode 100644 index 00000000..637426f0 --- /dev/null +++ b/stylesheets/font-awesome/scss/_screen-reader.scss @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { @include sr-only(); } +.sr-only-focusable { @include sr-only-focusable(); } diff --git a/stylesheets/font-awesome/scss/_variables.scss b/stylesheets/font-awesome/scss/_variables.scss index c10cd47f..1f374d6c 100644 --- a/stylesheets/font-awesome/scss/_variables.scss +++ b/stylesheets/font-awesome/scss/_variables.scss @@ -4,9 +4,9 @@ $fa-font-path: "../fonts" !default; $fa-font-size-base: 14px !default; $fa-line-height-base: 1 !default; -//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.4.0/fonts" !default; // for referencing Bootstrap CDN font files directly +//$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.6.1/fonts" !default; // for referencing Bootstrap CDN font files directly $fa-css-prefix: fa !default; -$fa-version: "4.4.0" !default; +$fa-version: "4.6.1" !default; $fa-border-color: #eee !default; $fa-inverse: #fff !default; $fa-li-width: (30em / 14) !default; @@ -20,6 +20,7 @@ $fa-var-align-left: "\f036"; $fa-var-align-right: "\f038"; $fa-var-amazon: "\f270"; $fa-var-ambulance: "\f0f9"; +$fa-var-american-sign-language-interpreting: "\f2a3"; $fa-var-anchor: "\f13d"; $fa-var-android: "\f17b"; $fa-var-angellist: "\f209"; @@ -50,8 +51,11 @@ $fa-var-arrows: "\f047"; $fa-var-arrows-alt: "\f0b2"; $fa-var-arrows-h: "\f07e"; $fa-var-arrows-v: "\f07d"; +$fa-var-asl-interpreting: "\f2a3"; +$fa-var-assistive-listening-systems: "\f2a2"; $fa-var-asterisk: "\f069"; $fa-var-at: "\f1fa"; +$fa-var-audio-description: "\f29e"; $fa-var-automobile: "\f1b9"; $fa-var-backward: "\f04a"; $fa-var-balance-scale: "\f24e"; @@ -86,12 +90,16 @@ $fa-var-bitbucket: "\f171"; $fa-var-bitbucket-square: "\f172"; $fa-var-bitcoin: "\f15a"; $fa-var-black-tie: "\f27e"; +$fa-var-blind: "\f29d"; +$fa-var-bluetooth: "\f293"; +$fa-var-bluetooth-b: "\f294"; $fa-var-bold: "\f032"; $fa-var-bolt: "\f0e7"; $fa-var-bomb: "\f1e2"; $fa-var-book: "\f02d"; $fa-var-bookmark: "\f02e"; $fa-var-bookmark-o: "\f097"; +$fa-var-braille: "\f2a1"; $fa-var-briefcase: "\f0b1"; $fa-var-btc: "\f15a"; $fa-var-bug: "\f188"; @@ -164,6 +172,7 @@ $fa-var-cny: "\f157"; $fa-var-code: "\f121"; $fa-var-code-fork: "\f126"; $fa-var-codepen: "\f1cb"; +$fa-var-codiepie: "\f284"; $fa-var-coffee: "\f0f4"; $fa-var-cog: "\f013"; $fa-var-cogs: "\f085"; @@ -182,6 +191,7 @@ $fa-var-copy: "\f0c5"; $fa-var-copyright: "\f1f9"; $fa-var-creative-commons: "\f25e"; $fa-var-credit-card: "\f09d"; +$fa-var-credit-card-alt: "\f283"; $fa-var-crop: "\f125"; $fa-var-crosshairs: "\f05b"; $fa-var-css3: "\f13c"; @@ -192,6 +202,8 @@ $fa-var-cutlery: "\f0f5"; $fa-var-dashboard: "\f0e4"; $fa-var-dashcube: "\f210"; $fa-var-database: "\f1c0"; +$fa-var-deaf: "\f2a4"; +$fa-var-deafness: "\f2a4"; $fa-var-dedent: "\f03b"; $fa-var-delicious: "\f1a5"; $fa-var-desktop: "\f108"; @@ -204,6 +216,7 @@ $fa-var-download: "\f019"; $fa-var-dribbble: "\f17d"; $fa-var-dropbox: "\f16b"; $fa-var-drupal: "\f1a9"; +$fa-var-edge: "\f282"; $fa-var-edit: "\f044"; $fa-var-eject: "\f052"; $fa-var-ellipsis-h: "\f141"; @@ -212,6 +225,7 @@ $fa-var-empire: "\f1d1"; $fa-var-envelope: "\f0e0"; $fa-var-envelope-o: "\f003"; $fa-var-envelope-square: "\f199"; +$fa-var-envira: "\f299"; $fa-var-eraser: "\f12d"; $fa-var-eur: "\f153"; $fa-var-euro: "\f153"; @@ -273,6 +287,7 @@ $fa-var-folder-open: "\f07c"; $fa-var-folder-open-o: "\f115"; $fa-var-font: "\f031"; $fa-var-fonticons: "\f280"; +$fa-var-fort-awesome: "\f286"; $fa-var-forumbee: "\f211"; $fa-var-forward: "\f04e"; $fa-var-foursquare: "\f180"; @@ -294,8 +309,11 @@ $fa-var-git-square: "\f1d2"; $fa-var-github: "\f09b"; $fa-var-github-alt: "\f113"; $fa-var-github-square: "\f092"; +$fa-var-gitlab: "\f296"; $fa-var-gittip: "\f184"; $fa-var-glass: "\f000"; +$fa-var-glide: "\f2a5"; +$fa-var-glide-g: "\f2a6"; $fa-var-globe: "\f0ac"; $fa-var-google: "\f1a0"; $fa-var-google-plus: "\f0d5"; @@ -319,6 +337,8 @@ $fa-var-hand-rock-o: "\f255"; $fa-var-hand-scissors-o: "\f257"; $fa-var-hand-spock-o: "\f259"; $fa-var-hand-stop-o: "\f256"; +$fa-var-hard-of-hearing: "\f2a4"; +$fa-var-hashtag: "\f292"; $fa-var-hdd-o: "\f0a0"; $fa-var-header: "\f1dc"; $fa-var-headphones: "\f025"; @@ -390,6 +410,7 @@ $fa-var-long-arrow-down: "\f175"; $fa-var-long-arrow-left: "\f177"; $fa-var-long-arrow-right: "\f178"; $fa-var-long-arrow-up: "\f176"; +$fa-var-low-vision: "\f2a8"; $fa-var-magic: "\f0d0"; $fa-var-magnet: "\f076"; $fa-var-mail-forward: "\f064"; @@ -418,8 +439,10 @@ $fa-var-minus: "\f068"; $fa-var-minus-circle: "\f056"; $fa-var-minus-square: "\f146"; $fa-var-minus-square-o: "\f147"; +$fa-var-mixcloud: "\f289"; $fa-var-mobile: "\f10b"; $fa-var-mobile-phone: "\f10b"; +$fa-var-modx: "\f285"; $fa-var-money: "\f0d6"; $fa-var-moon-o: "\f186"; $fa-var-mortar-board: "\f19d"; @@ -446,11 +469,14 @@ $fa-var-paperclip: "\f0c6"; $fa-var-paragraph: "\f1dd"; $fa-var-paste: "\f0ea"; $fa-var-pause: "\f04c"; +$fa-var-pause-circle: "\f28b"; +$fa-var-pause-circle-o: "\f28c"; $fa-var-paw: "\f1b0"; $fa-var-paypal: "\f1ed"; $fa-var-pencil: "\f040"; $fa-var-pencil-square: "\f14b"; $fa-var-pencil-square-o: "\f044"; +$fa-var-percent: "\f295"; $fa-var-phone: "\f095"; $fa-var-phone-square: "\f098"; $fa-var-photo: "\f03e"; @@ -472,11 +498,13 @@ $fa-var-plus-square: "\f0fe"; $fa-var-plus-square-o: "\f196"; $fa-var-power-off: "\f011"; $fa-var-print: "\f02f"; +$fa-var-product-hunt: "\f288"; $fa-var-puzzle-piece: "\f12e"; $fa-var-qq: "\f1d6"; $fa-var-qrcode: "\f029"; $fa-var-question: "\f128"; $fa-var-question-circle: "\f059"; +$fa-var-question-circle-o: "\f29c"; $fa-var-quote-left: "\f10d"; $fa-var-quote-right: "\f10e"; $fa-var-ra: "\f1d0"; @@ -484,6 +512,7 @@ $fa-var-random: "\f074"; $fa-var-rebel: "\f1d0"; $fa-var-recycle: "\f1b8"; $fa-var-reddit: "\f1a1"; +$fa-var-reddit-alien: "\f281"; $fa-var-reddit-square: "\f1a2"; $fa-var-refresh: "\f021"; $fa-var-registered: "\f25d"; @@ -508,6 +537,7 @@ $fa-var-rupee: "\f156"; $fa-var-safari: "\f267"; $fa-var-save: "\f0c7"; $fa-var-scissors: "\f0c4"; +$fa-var-scribd: "\f28a"; $fa-var-search: "\f002"; $fa-var-search-minus: "\f010"; $fa-var-search-plus: "\f00e"; @@ -525,10 +555,14 @@ $fa-var-sheqel: "\f20b"; $fa-var-shield: "\f132"; $fa-var-ship: "\f21a"; $fa-var-shirtsinbulk: "\f214"; +$fa-var-shopping-bag: "\f290"; +$fa-var-shopping-basket: "\f291"; $fa-var-shopping-cart: "\f07a"; $fa-var-sign-in: "\f090"; +$fa-var-sign-language: "\f2a7"; $fa-var-sign-out: "\f08b"; $fa-var-signal: "\f012"; +$fa-var-signing: "\f2a7"; $fa-var-simplybuilt: "\f215"; $fa-var-sitemap: "\f0e8"; $fa-var-skyatlas: "\f216"; @@ -537,6 +571,9 @@ $fa-var-slack: "\f198"; $fa-var-sliders: "\f1de"; $fa-var-slideshare: "\f1e7"; $fa-var-smile-o: "\f118"; +$fa-var-snapchat: "\f2ab"; +$fa-var-snapchat-ghost: "\f2ac"; +$fa-var-snapchat-square: "\f2ad"; $fa-var-soccer-ball-o: "\f1e3"; $fa-var-sort: "\f0dc"; $fa-var-sort-alpha-asc: "\f15d"; @@ -572,6 +609,8 @@ $fa-var-stethoscope: "\f0f1"; $fa-var-sticky-note: "\f249"; $fa-var-sticky-note-o: "\f24a"; $fa-var-stop: "\f04d"; +$fa-var-stop-circle: "\f28d"; +$fa-var-stop-circle-o: "\f28e"; $fa-var-street-view: "\f21d"; $fa-var-strikethrough: "\f0cc"; $fa-var-stumbleupon: "\f1a4"; @@ -636,12 +675,14 @@ $fa-var-twitter-square: "\f081"; $fa-var-umbrella: "\f0e9"; $fa-var-underline: "\f0cd"; $fa-var-undo: "\f0e2"; +$fa-var-universal-access: "\f29a"; $fa-var-university: "\f19c"; $fa-var-unlink: "\f127"; $fa-var-unlock: "\f09c"; $fa-var-unlock-alt: "\f13e"; $fa-var-unsorted: "\f0dc"; $fa-var-upload: "\f093"; +$fa-var-usb: "\f287"; $fa-var-usd: "\f155"; $fa-var-user: "\f007"; $fa-var-user-md: "\f0f0"; @@ -653,11 +694,14 @@ $fa-var-venus: "\f221"; $fa-var-venus-double: "\f226"; $fa-var-venus-mars: "\f228"; $fa-var-viacoin: "\f237"; +$fa-var-viadeo: "\f2a9"; +$fa-var-viadeo-square: "\f2aa"; $fa-var-video-camera: "\f03d"; $fa-var-vimeo: "\f27d"; $fa-var-vimeo-square: "\f194"; $fa-var-vine: "\f1ca"; $fa-var-vk: "\f189"; +$fa-var-volume-control-phone: "\f2a0"; $fa-var-volume-down: "\f027"; $fa-var-volume-off: "\f026"; $fa-var-volume-up: "\f028"; @@ -667,11 +711,14 @@ $fa-var-weibo: "\f18a"; $fa-var-weixin: "\f1d7"; $fa-var-whatsapp: "\f232"; $fa-var-wheelchair: "\f193"; +$fa-var-wheelchair-alt: "\f29b"; $fa-var-wifi: "\f1eb"; $fa-var-wikipedia-w: "\f266"; $fa-var-windows: "\f17a"; $fa-var-won: "\f159"; $fa-var-wordpress: "\f19a"; +$fa-var-wpbeginner: "\f297"; +$fa-var-wpforms: "\f298"; $fa-var-wrench: "\f0ad"; $fa-var-xing: "\f168"; $fa-var-xing-square: "\f169"; diff --git a/stylesheets/font-awesome/scss/font-awesome.scss b/stylesheets/font-awesome/scss/font-awesome.scss index ebd9646c..a19d664c 100644 --- a/stylesheets/font-awesome/scss/font-awesome.scss +++ b/stylesheets/font-awesome/scss/font-awesome.scss @@ -1,5 +1,5 @@ /*! - * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.6.1 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ @@ -15,3 +15,4 @@ @import "rotated-flipped"; @import "stacked"; @import "icons"; +@import "screen-reader"; -- cgit v1.3 From f7a47f1b9e681a7ad1d2401fb779447cdce2a341 Mon Sep 17 00:00:00 2001 From: 27chan <27chan@openmailbox.org> Date: Fri, 29 Apr 2016 15:40:01 -0300 Subject: New theme northboard_cb For circleboard by kotiakrobaatti --- stylesheets/northboard_cb.css | 249 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 stylesheets/northboard_cb.css diff --git a/stylesheets/northboard_cb.css b/stylesheets/northboard_cb.css new file mode 100644 index 00000000..b665c569 --- /dev/null +++ b/stylesheets/northboard_cb.css @@ -0,0 +1,249 @@ +/** +* northboard.css +* for circleboard by kotiakrobaatti +*/ + +body { + background: #191919; + color: #d4d4d4; + font-size: 13px; + font: 12px/1.5em Verdana,"DejaVu Sans",Sans-serif; +} + +a:link, a:visited, .intro a.email { + color: #60a0dc !important; + text-decoration: none; +} + +a:link:hover { + outline: 0; + color: #82c2fe !important; +} + +a.post_no, a.watchThread { + color: #60a0dc; +} + +div.post.reply { + background: #343439; + border-color: #3070A9; + border-top: 1px solid #3070A9; + border-left: 1px solid #3070A9; + border-radius: 3px; + padding: 0px; +} + +div.post.reply.highlighted { + background: #44444f; + border: 3px dashed #3070a9; +} + +div.post.reply div.body a, .mentioned { + color: 60a0dc; + text-decoration: underline; +} + +div.post.reply div.body a:hover { + color: #82c2fe !important; + text-decoration: underline !important; +} + +div.body a:hover, a.watchThread:hover, a.email span.name:hover { + color: #82c2fe !important; +} + +.intro span.subject { + color: #60a0dc !important; + font-weight: 700; + font-size: 1.4em; + line-height: 35px; + height: 35px; + padding: 0 5px; +} + +form table tr th { + background: #333; + border: 1px solid #444; + color: #60a0dc; +} + +div.ban h2 { + background: #333; + color: inherit; +} + +div.ban { + border-color: #800; +} + +div.ban p { + color: black; +} + +div.pages { + background: #343439; + border: 1px solid #3070a9; + border-radius: 5px; +} + +div.pages a.selected { + color: #ccc; +} + +div.boardlist { + color: #aaa; +} + +div.boardlist a { + color: #ccc; +} + +table.modlog tr th { + background: #333; +} + +/* options.js */ + +#options_div, #alert_div { + background: #44444f; +} + +.desktop-style div.boardlist:nth-child(1) { + background-color: #bbb; +} + +span.name { + color: #93e3ff !important; +} + +span.trip { + color: #ffffff; +} + +span.quote { + color: green; +} + +[id^=thread] { + background: #1C1C1C; + margin-bottom: 1em ; + padding: 1em 1em 0 1em ; + border: 1px solid #333333; +} + +[id^=thread] hr { + display: none ; +} + +html body hr { + border-color: #9B9B9B; + border-width: 1px; +} + +p.intro { + background-color: #44444F; + padding-top: 5px; + padding-left: 5px; + padding-right: 5px; + margin-top: 0px!important; + display: box; + margin: intial; + border-bottom: 1px solid #1c1c1c; + box-shadow: 0 5px 5px -5px #1c1c1c; +} + +div.post.op p.intro { + background-color: #252525; + min-height: 30px; +} + +span.omitted { + color: #707070; + font-size: .9em; + margin: 5 5px 0px; +} + +h1 { + font-size: 3em; + font-weight: 300; + margin: .3em 0; + color: #60a0dc; +} + +div.subtitle { + font-style: oblique; + font-size: 1.25em!important; + font-weight: 400!important; + color: #ccc!important; +} + +tr td input, #body { + background-color: #AAA; + border-color: #888; + border-width: 1px; + padding-top: 3px; + padding-right: 2px; + padding-bottom: 3px; + padding-left: 2px; +} + +#upload_file { + color: #ccc; + background-color: transparent; +} + +div.blotter { + color: #ccc!important; +} + +div.banner, a.unimportant { + background-color: #60a0dc; + color: #000!important; + border-radius: 3px; + min-height: 25px; + padding-top: 5px; +} + +div.thread:hover { + background: #44444f; + border-color: #1c1c1c; +} + +span.heading { + color: red; + text-shadow: 0 0 3px #000, 0 0 3px #000; +} + +#expand-all-images, +#shrink-all-images, +#treeview{ + right: 1em !important; + position: absolute !important; +} + +#expand-all-images{ + margin-top: 4em !important; +} + +#treeview{ + margin-top: 5em !important; +} + +#shrink-all-images{ + margin-top: 6em !important; +} + +#expand-all-images + hr, +#shrink-all-images + hr{ + opacity: 0 !important; + margin: 0 !important; +} + +#treeview + hr{ + opacity: 0 !important; + clear: both !important; +} + +#options_handler{ + margin-top: 3em !important; +} -- cgit v1.3 From 199931dc1a609503932848be9a276e1d2275d881 Mon Sep 17 00:00:00 2001 From: 27chan <27chan@openmailbox.org> Date: Fri, 29 Apr 2016 18:00:33 -0300 Subject: Fix default theme and add Font Awesome Icons --- templates/themes/recent/recent.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/themes/recent/recent.html b/templates/themes/recent/recent.html index 31c839e7..75880484 100644 --- a/templates/themes/recent/recent.html +++ b/templates/themes/recent/recent.html @@ -7,6 +7,8 @@ {% if config.url_favicon %}{% endif %} + {% if config.default_stylesheet.1 != '' %}{% endif %} + {% if config.font_awesome %}{% endif %} {{ boardlist.top }} -- cgit v1.3 From 7c3126866cd291d515ab5cc83d4635c3c95c6d85 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 06:43:22 +0200 Subject: ease the migration process for the previous security patch (by introducing another migration); restore php 5.4 compatibility (introducing a polyfill system) --- inc/functions.php | 2 ++ inc/mod/auth.php | 10 +++++----- inc/mod/pages.php | 18 +++++++++--------- inc/polyfill.php | 28 ++++++++++++++++++++++++++++ install.php | 4 +++- install.sql | 2 +- 6 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 inc/polyfill.php diff --git a/inc/functions.php b/inc/functions.php index 515e3e55..3f5ed3b0 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -18,6 +18,8 @@ require_once 'inc/template.php'; require_once 'inc/database.php'; require_once 'inc/events.php'; require_once 'inc/api.php'; +require_once 'inc/polyfill.php'; + if (!extension_loaded('gettext')) { require_once 'inc/lib/gettext/gettext.inc'; } diff --git a/inc/mod/auth.php b/inc/mod/auth.php index fa1a0f4f..d877b89f 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -76,20 +76,20 @@ function generate_salt() { function login($username, $password) { global $mod, $config; - $query = prepare("SELECT `id`, `type`, `boards`, `password`, `salt` FROM ``mods`` WHERE `username` = :username"); + $query = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE `username` = :username"); $query->bindValue(':username', $username); $query->execute() or error(db_error($query)); if ($user = $query->fetch(PDO::FETCH_ASSOC)) { - list($version, $ok) = test_password($user['password'], $user['salt'], $password); + list($version, $ok) = test_password($user['password'], $user['version'], $password); if ($ok) { if ($config['password_crypt_version'] > $version) { // It's time to upgrade the password hashing method! - list ($user['salt'], $user['password']) = crypt_password($password); - $query = prepare("UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id"); + list ($user['version'], $user['password']) = crypt_password($password); + $query = prepare("UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id"); $query->bindValue(':password', $user['password']); - $query->bindValue(':salt', $user['salt']); + $query->bindValue(':version', $user['version']); $query->bindValue(':id', $user['id']); $query->execute() or error(db_error($query)); } diff --git a/inc/mod/pages.php b/inc/mod/pages.php index a07de4c7..8b6f73c4 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1734,12 +1734,12 @@ function mod_user($uid) { } if ($_POST['password'] != '') { - list($salt, $password) = crypt_password($_POST['password']); + list($version, $password) = crypt_password($_POST['password']); - $query = prepare('UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id'); + $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id'); $query->bindValue(':id', $uid); $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); + $query->bindValue(':version', $version); $query->execute() or error(db_error($query)); modLog('Changed password for ' . utf8tohtml($_POST['username']) . ' (#' . $user['id'] . ')'); @@ -1760,12 +1760,12 @@ function mod_user($uid) { if (hasPermission($config['mod']['change_password']) && $uid == $mod['id'] && isset($_POST['password'])) { if ($_POST['password'] != '') { - list($salt, $password) = crypt_password($_POST['password']); + list($version, $password) = crypt_password($_POST['password']); - $query = prepare('UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id'); + $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id'); $query->bindValue(':id', $uid); $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); + $query->bindValue(':version', $version); $query->execute() or error(db_error($query)); modLog('Changed own password'); @@ -1832,12 +1832,12 @@ function mod_user_new() { if (!isset($config['mod']['groups'][$type]) || $type == DISABLED) error(sprintf($config['error']['invalidfield'], 'type')); - list($salt, $password) = crypt_password($_POST['password']); + list($version, $password) = crypt_password($_POST['password']); - $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, :type, :boards)'); + $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :version, :type, :boards)'); $query->bindValue(':username', $_POST['username']); $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); + $query->bindValue(':version', $version); $query->bindValue(':type', $type); $query->bindValue(':boards', implode(',', $boards)); $query->execute() or error(db_error($query)); diff --git a/inc/polyfill.php b/inc/polyfill.php new file mode 100644 index 00000000..ac40a00a --- /dev/null +++ b/inc/polyfill.php @@ -0,0 +1,28 @@ + $i ? $i : 0]) ^ ord($theirs[$i]); + } + + return $answer === 0 && $olen === $tlen; + } +} diff --git a/install.php b/install.php index 5a2d724a..968c41de 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); diff --git a/install.sql b/install.sql index fbf220c1..7e661450 100644 --- a/install.sql +++ b/install.sql @@ -132,7 +132,7 @@ CREATE TABLE IF NOT EXISTS `mods` ( `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(30) NOT NULL, `password` varchar(256) CHARACTER SET ascii NOT NULL COMMENT 'SHA256', - `salt` varchar(64) CHARACTER SET ascii NOT NULL, + `version` varchar(64) CHARACTER SET ascii NOT NULL, `type` smallint(2) NOT NULL, `boards` text CHARACTER SET utf8 NOT NULL, PRIMARY KEY (`id`), -- cgit v1.3 From 97681613274c35061bcbd8d14e03600d5fb18345 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 07:51:55 +0200 Subject: simplify the code a bit --- inc/display.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/inc/display.php b/inc/display.php index 90cf3434..58cb4805 100644 --- a/inc/display.php +++ b/inc/display.php @@ -94,21 +94,16 @@ function error($message, $priority = true, $debug_stuff = false) { $debug_stuff['backtrace'] = debug_backtrace(); } - // Return the bad request header, necessary for AJAX posts - // czaks: is it really so? the ajax errors only work when this is commented out - // better yet use it when ajax is disabled - if (!isset ($_POST['json_response'])) { - header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request'); - } - - // Is there a reason to disable this? if (isset($_POST['json_response'])) { header('Content-Type: text/json; charset=utf-8'); die(json_encode(array( 'error' => $message ))); } - + else { + header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request'); + } + $pw = $config['db']['password']; $debug_callback = function(&$item) use (&$debug_callback, $pw) { if (is_array($item)) { -- cgit v1.3 From dcf5d699bd6ce5770ebd515cc14219f3015dd63a Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 08:21:21 +0200 Subject: simplify the md5 execution logic --- inc/api.php | 7 ++++++- inc/config.php | 4 ++-- post.php | 44 ++++++++++++++++++++++---------------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/inc/api.php b/inc/api.php index 57e4d367..9483200e 100644 --- a/inc/api.php +++ b/inc/api.php @@ -92,7 +92,12 @@ class Api { $dotPos = strrpos($file->file, '.'); $apiPost['ext'] = substr($file->file, $dotPos); $apiPost['tim'] = substr($file->file, 0, $dotPos); - $apiPost['md5'] = base64_encode(hex2bin($post->filehash)); + if (isset ($file->hash) && $post->filehash) { + $apiPost['md5'] = base64_encode(hex2bin($file->hash)); + } + else if (isset ($post->filehash) && $post->filehash) { + $apiPost['md5'] = base64_encode(hex2bin($post->filehash)); + } } private function translatePost($post, $threadsPage = false) { diff --git a/inc/config.php b/inc/config.php index c0e967af..b92c4ac0 100644 --- a/inc/config.php +++ b/inc/config.php @@ -807,8 +807,8 @@ // Set this to true if you're using a BSD $config['bsd_md5'] = false; - // Set this to true if you're having problems with image duplicated error and bsd_md5 doesn't help. - $config['php_md5'] = false; + // Set this to true if you're using Linux and you can execute `md5sum` binary. + $config['gnu_md5'] = false; // Number of posts in a "View Last X Posts" page $config['noko50_count'] = 50; diff --git a/post.php b/post.php index 729170a7..5c755a3c 100644 --- a/post.php +++ b/post.php @@ -572,7 +572,12 @@ if (isset($_POST['delete'])) { if ($post['has_file']) { - $fnarray = array(); + $md5cmd = false; + if ($config['bsd_md5']) $md5cmd = 'md5 -r'; + if ($config['gnu_md5']) $md5cmd = 'md5sum'; + + $allhashes = ''; + foreach ($post['files'] as $key => &$file) { if ($post['op'] && $config['allowed_ext_op']) { if (!in_array($file['extension'], $config['allowed_ext_op'])) @@ -586,34 +591,29 @@ if (isset($_POST['delete'])) { // Truncate filename if it is too long $file['filename'] = mb_substr($file['filename'], 0, $config['max_filename_len']); - if (!isset($filenames)) { - $filenames = escapeshellarg($file['tmp_name']); - } else { - $filenames .= (' ' . escapeshellarg($file['tmp_name'])); - } - - $fnarray[] = $file['tmp_name']; - $upload = $file['tmp_name']; if (!is_readable($upload)) error($config['error']['nomove']); + + if ($md5cmd) { + $output = shell_exec_error($md5cmd . " < " . escapeshellarg($upload)); + $output = explode(' ', $output); + $hash = $output[0]; + } + else { + $hash = md5_file($upload); + } + + $file['hash'] = $hash; + $allhashes .= $hash; } - - $md5cmd = $config['bsd_md5'] ? 'md5 -r' : 'md5sum'; - if (!$config['php_md5'] && $output = shell_exec_error("cat $filenames | $md5cmd")) { - $explodedvar = explode(' ', $output); - $hash = $explodedvar[0]; + if (count ($post['files']) == 1) { $post['filehash'] = $hash; - } elseif ($config['max_images'] === 1) { - $post['filehash'] = md5_file($upload); - } else { - $str_to_hash = ''; - foreach ($fnarray as $i => $f) { - $str_to_hash .= file_get_contents($f); - } - $post['filehash'] = md5($str_to_hash); + } + else { + $post['filehash'] = md5($allhashes); } } -- cgit v1.3 From 36b78e5f98302cb8fb3d4a00d4be61d6775d74a3 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 08:40:13 +0200 Subject: fix for editor highlighting --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 3f5ed3b0..fd4c910d 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -138,7 +138,7 @@ function loadConfig() { $configstr .= file_get_contents($board['dir'] . '/config.php'); } $matches = array(); - preg_match_all('/[^\/*#]\$config\s*\[\s*[\'"]locale[\'"]\s*\]\s*=\s*([\'"])(.*?)\1/', $configstr, $matches); + preg_match_all('/[^\/#*]\$config\s*\[\s*[\'"]locale[\'"]\s*\]\s*=\s*([\'"])(.*?)\1/', $configstr, $matches); if ($matches && isset ($matches[2]) && $matches[2]) { $matches = $matches[2]; $config['locale'] = $matches[count($matches)-1]; -- cgit v1.3 From a42256b296f14c5f82889f9e222d94a416aa1924 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 08:43:34 +0200 Subject: locale cache: fix a bug when perms are done wrong --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index fd4c910d..462bc173 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -127,7 +127,7 @@ function loadConfig() { // So, we may store the locale in a tmp/ filesystem. if (file_exists($fn = 'tmp/cache/locale_' . $boardsuffix ) ) { - $config['locale'] = file_get_contents($fn); + $config['locale'] = @file_get_contents($fn); } else { $config['locale'] = 'en'; @@ -144,7 +144,7 @@ function loadConfig() { $config['locale'] = $matches[count($matches)-1]; } - file_put_contents($fn, $config['locale']); + @file_put_contents($fn, $config['locale']); } if ($config['locale'] != $current_locale) { -- cgit v1.3 From 8dac72e924d658e66c5efe3334aa8ab41d00daca Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 09:16:09 +0200 Subject: update installer --- install.php | 103 +++++++++++++++++++++++++++------------- templates/installer/config.html | 3 ++ 2 files changed, 73 insertions(+), 33 deletions(-) diff --git a/install.php b/install.php index 968c41de..7a146c8e 100644 --- a/install.php +++ b/install.php @@ -581,6 +581,25 @@ if (file_exists($config['has_installed'])) { die(Element('page.html', $page)); } +function create_config_from_array(&$instance_config, &$array, $prefix = '') { + foreach ($array as $name => $value) { + if (is_array($value)) { + $instance_config .= "\n"; + create_config_from_array($instance_config, $value, $prefix . '[\'' . addslashes($name) . '\']'); + $instance_config .= "\n"; + } else { + $instance_config .= ' $config' . $prefix . '[\'' . addslashes($name) . '\'] = '; + + if (is_numeric($value)) + $instance_config .= $value; + else + $instance_config .= "'" . addslashes($value) . "'"; + + $instance_config .= ";\n"; + } + } +} + if ($step == 0) { // Agreeement $page['body'] = ' @@ -614,7 +633,7 @@ if ($step == 0) { 'installed' => extension_loaded('pdo'), 'required' => true ), - 'PDO' => array( + 'GD' => array( 'installed' => extension_loaded('gd'), 'required' => true ), @@ -627,17 +646,17 @@ if ($step == 0) { $tests = array( array( 'category' => 'PHP', - 'name' => 'PHP ≥ 5.3', - 'result' => PHP_VERSION_ID >= 50300, + 'name' => 'PHP ≥ 5.4', + 'result' => PHP_VERSION_ID >= 50400, 'required' => true, - 'message' => 'vichan requires PHP 5.3 or better.', + 'message' => 'vichan requires PHP 5.4 or better.', ), array( 'category' => 'PHP', - 'name' => 'PHP ≥ 5.4', - 'result' => PHP_VERSION_ID >= 50400, + 'name' => 'PHP ≥ 5.6', + 'result' => PHP_VERSION_ID >= 50600, 'required' => false, - 'message' => 'vichan works best on PHP 5.4 or better.', + 'message' => 'vichan works best on PHP 5.6 or better.', ), array( 'category' => 'PHP', @@ -694,6 +713,7 @@ if ($step == 0) { 'result' => $can_exec && shell_exec('which convert'), 'required' => false, 'message' => '(Optional) `convert` was not found or executable; command-line ImageMagick image processing cannot be enabled.', + 'effect' => function (&$config) { $config['thumb_method'] = 'convert'; }, ), array( 'category' => 'Image processing', @@ -708,6 +728,7 @@ if ($step == 0) { 'result' => $can_exec && shell_exec('which gm'), 'required' => false, 'message' => '(Optional) `gm` was not found or executable; command-line GraphicsMagick (faster than ImageMagick) cannot be enabled.', + 'effect' => function (&$config) { $config['thumb_method'] = 'gm'; }, ), array( 'category' => 'Image processing', @@ -715,13 +736,25 @@ if ($step == 0) { 'result' => $can_exec && shell_exec('which gifsicle'), 'required' => false, 'message' => '(Optional) `gifsicle` was not found or executable; you may not use `convert+gifsicle` for better animated GIF thumbnailing.', + 'effect' => function (&$config) { if ($config['thumb_method'] == 'gm') $config['thumb_method'] = 'gm+gifsicle'; + if ($config['thumb_method'] == 'convert') $config['thumb_method'] = 'convert+gifsicle'; }, ), array( 'category' => 'Image processing', - 'name' => '`md5sum` (quick file hashing)', + 'name' => '`md5sum` (quick file hashing on GNU/Linux)', + 'prereq' => '', 'result' => $can_exec && shell_exec('echo "vichan" | md5sum') == "141225c362da02b5c359c45b665168de -\n", 'required' => false, - 'message' => '(Optional) `md5sum` was not found or executable; file hashing for multiple images will be slower.', + 'message' => '(Optional) `md5sum` was not found or executable; file hashing for multiple images will be slower. Ignore if not using Linux.', + 'effect' => function (&$config) { $config['gnu_md5'] = true; }, + ), + array( + 'category' => 'Image processing', + 'name' => '`/sbin/md5` (quick file hashing on BSDs)', + 'result' => $can_exec && shell_exec('echo "vichan" | /sbin/md5 -r') == "141225c362da02b5c359c45b665168de\n", + 'required' => false, + 'message' => '(Optional) `/sbin/md5` was not found or executable; file hashing for multiple images will be slower. Ignore if not using BSD.', + 'effect' => function (&$config) { $config['bsd_md5'] = true; }, ), array( 'category' => 'File permissions', @@ -737,6 +770,13 @@ if ($step == 0) { 'required' => true, 'message' => 'You must give vichan permission to create (and write to) the templates/cache directory or performance will be drastically reduced.' ), + array( + 'category' => 'File permissions', + 'name' => getcwd() . '/tmp/cache', + 'result' => is_dir('tmp/cache') && is_writable('tmp/cache'), + 'required' => true, + 'message' => 'You must give vichan permission to write to the tmp/cache directory.' + ), array( 'category' => 'File permissions', 'name' => getcwd() . '/inc/instance-config.php', @@ -760,17 +800,27 @@ if ($step == 0) { 'message' => 'vichan is still beta software and it\'s not going to come out of beta any time soon. As there are often many months between releases yet changes and bug fixes are very frequent, it\'s recommended to use the git repository to maintain your vichan installation. Using git makes upgrading much easier.' ) ); - + $config['font_awesome'] = true; + $additional_config = array(); + foreach ($tests as $test) { + if ($test['result'] && $test['effect']) { + $test['effect']($additional_config); + } + } + $more = ''; + create_config_from_array($more, $additional_config); + $_SESSION['more'] = $more; + echo Element('page.html', array( 'body' => Element('installer/check-requirements.html', array( 'extensions' => $extensions, 'tests' => $tests, - 'config' => $config + 'config' => $config, )), 'title' => 'Checking environment', - 'config' => $config + 'config' => $config, )); } elseif ($step == 2) { // Basic config @@ -781,14 +831,18 @@ if ($step == 0) { echo Element('page.html', array( 'body' => Element('installer/config.html', array( - 'config' => $config + 'config' => $config, + 'more' => $more, )), 'title' => 'Configuration', 'config' => $config )); } elseif ($step == 3) { + $more = $_POST['more']; + unset($_POST['more']); + $instance_config = -' $value) { - if (is_array($value)) { - $instance_config .= "\n"; - create_config_from_array($instance_config, $value, $prefix . '[\'' . addslashes($name) . '\']'); - $instance_config .= "\n"; - } else { - $instance_config .= ' $config' . $prefix . '[\'' . addslashes($name) . '\'] = '; - - if (is_numeric($value)) - $instance_config .= $value; - else - $instance_config .= "'" . addslashes($value) . "'"; - - $instance_config .= ";\n"; - } - } - } - create_config_from_array($instance_config, $_POST); + $instance_config .= "\n"; + $instance_config .= $more; $instance_config .= "\n"; if (@file_put_contents('inc/instance-config.php', $instance_config)) { diff --git a/templates/installer/config.html b/templates/installer/config.html index 193bb98b..973328f5 100644 --- a/templates/installer/config.html +++ b/templates/installer/config.html @@ -87,6 +87,9 @@ Miscellaneous + + +

-- cgit v1.3 From 985c113190f077ffe60abbf89642543851076eb9 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 09:18:36 +0200 Subject: ... --- install.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.php b/install.php index 7a146c8e..21c573d1 100644 --- a/install.php +++ b/install.php @@ -805,7 +805,7 @@ if ($step == 0) { $additional_config = array(); foreach ($tests as $test) { - if ($test['result'] && $test['effect']) { + if ($test['result'] && isset($test['effect'])) { $test['effect']($additional_config); } } -- cgit v1.3 From 89fe3db55608385cb4a7ba8d7ac693d6fe75ff0a Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 09:25:47 +0200 Subject: ... --- install.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/install.php b/install.php index 21c573d1..3aa8f56f 100644 --- a/install.php +++ b/install.php @@ -600,6 +600,8 @@ function create_config_from_array(&$instance_config, &$array, $prefix = '') { } } +session_start(); + if ($step == 0) { // Agreeement $page['body'] = ' @@ -832,7 +834,7 @@ if ($step == 0) { echo Element('page.html', array( 'body' => Element('installer/config.html', array( 'config' => $config, - 'more' => $more, + 'more' => $_SESSION['more'], )), 'title' => 'Configuration', 'config' => $config -- cgit v1.3 From 38bf3276e4ffd4b82c4a7bc9eb8e1cf692147bb2 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 5 May 2016 09:39:23 +0200 Subject: update copyright years; remove a link to tinyboard (website is dead) --- templates/generic_page.html | 8 ++++---- templates/index.html | 8 ++++---- templates/page.html | 8 ++++---- templates/themes/basic/index.html | 8 ++++---- templates/themes/catalog/catalog.html | 8 ++++---- templates/themes/categories/news.html | 8 ++++---- templates/themes/frameset/news.html | 8 ++++---- templates/themes/recent/recent.html | 8 ++++---- templates/thread.html | 8 ++++---- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/templates/generic_page.html b/templates/generic_page.html index 965cbc2c..35b58df3 100644 --- a/templates/generic_page.html +++ b/templates/generic_page.html @@ -36,10 +36,10 @@ {% endfor %} {{ btn.next }} {{ boardlist.bottom }}

-

- Tinyboard + - vichan {{ config.version }} - -
Tinyboard Copyright © 2010-2014 Tinyboard Development Group -
vichan Copyright © 2012-2015 vichan-devel

+

- Tinyboard + + vichan {{ config.version }} - +
Tinyboard Copyright © 2010-2014 Tinyboard Development Group +
vichan Copyright © 2012-2016 vichan-devel

{% for footer in config.footer %}

{{ footer }}

{% endfor %}
diff --git a/templates/main.js b/templates/main.js index 7e3b2dc1..c2a6f6c0 100644 --- a/templates/main.js +++ b/templates/main.js @@ -115,7 +115,10 @@ var styles = { {% for stylesheet in stylesheets %}{% raw %}'{% endraw %}{{ stylesheet.name|addslashes }}{% raw %}' : '{% endraw %}{{ stylesheet.uri|addslashes }}{% raw %}', {% endraw %}{% endfor %}{% raw %} }; -var board_name = false; + +if (typeof board_name === 'undefined') { + var board_name = false; +} function changeStyle(styleName, link) { {% endraw %} @@ -159,13 +162,7 @@ function changeStyle(styleName, link) { {% endraw %} {% if config.stylesheets_board %} - {# This is such an unacceptable mess. There needs to be an easier way. #} - {# Needs fix for slugify #} - var matches = document.URL.match(/\/(\w+)\/($|{{ config.dir.res|replace({'/': '\\/'}) }}{{ config.file_page|replace({'%d': '\\d+', '.': '\\.'}) }}|{{ config.file_index|replace({'.': '\\.'}) }}|{{ config.file_page|replace({'%d': '\\d+', '.': '\\.'}) }})/); {% raw %} - if (matches) { - board_name = matches[1]; - } if (!localStorage.board_stylesheets) { localStorage.board_stylesheets = '{}'; diff --git a/templates/page.html b/templates/page.html index 3522702f..816bb72b 100644 --- a/templates/page.html +++ b/templates/page.html @@ -3,7 +3,7 @@ {% include 'header.html' %} {{ title }} diff --git a/templates/themes/catalog/catalog.html b/templates/themes/catalog/catalog.html index 1011fdaf..1f5e8b05 100644 --- a/templates/themes/catalog/catalog.html +++ b/templates/themes/catalog/catalog.html @@ -4,7 +4,8 @@ {% include 'header.html' %} {{ settings.title }} diff --git a/templates/thread.html b/templates/thread.html index b7bb5f6b..506b9cc2 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -4,7 +4,9 @@ {% include 'header.html' %} -- cgit v1.3 From d46bf4e2f2209a38e11dad436b0abb5d89ef4448 Mon Sep 17 00:00:00 2001 From: Bui Date: Fri, 3 Oct 2014 00:12:15 +0900 Subject: add id to thread links --- templates/thread.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/templates/thread.html b/templates/thread.html index 506b9cc2..0899c414 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -49,8 +49,12 @@ {{ body }} {% include 'report_delete.html' %} - [{% trans %}Return{% endtrans %}] - [{% trans %}Go to top{% endtrans %}] + + + [{% trans %}Return{% endtrans %}] + [{% trans %}Go to top{% endtrans %}] + [{% trans %}Catalog{% endtrans %}] + {{ boardlist.bottom }} -- cgit v1.3 From 6c334a3b4494a9b1b3e9f801dab45e6e027dceed Mon Sep 17 00:00:00 2001 From: Bui Date: Fri, 3 Oct 2014 00:16:41 +0900 Subject: lol spaces --- templates/thread.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/thread.html b/templates/thread.html index 0899c414..c909f843 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -52,9 +52,9 @@ [{% trans %}Return{% endtrans %}] - [{% trans %}Go to top{% endtrans %}] - [{% trans %}Catalog{% endtrans %}] - + [{% trans %}Go to top{% endtrans %}] + [{% trans %}Catalog{% endtrans %}] + {{ boardlist.bottom }} -- cgit v1.3 From 293543878a10e1ef93990acb373f8f22ca655f64 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 12:10:50 +0200 Subject: backport parts of 2d6d449bd2d72, in particular html classes --- stylesheets/style.css | 2 +- templates/generic_page.html | 2 +- templates/index.html | 2 +- templates/page.html | 2 +- templates/post_thread.html | 2 +- templates/themes/basic/index.html | 2 +- templates/thread.html | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/stylesheets/style.css b/stylesheets/style.css index 6b4b19d9..2c5663bd 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -666,7 +666,7 @@ pre { margin-left: -20px; } -div.thread:hover { +.theme-catalog div.thread:hover { background: #D6DAF0; border-color: #B7C5D9; } diff --git a/templates/generic_page.html b/templates/generic_page.html index 35b58df3..fbfc3c50 100644 --- a/templates/generic_page.html +++ b/templates/generic_page.html @@ -6,7 +6,7 @@ {{ board.url }} - {{ board.name }} {% endblock %} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} {% if config.url_banner %}{% endif %} diff --git a/templates/index.html b/templates/index.html index d4c56707..1eea6e6b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -16,7 +16,7 @@ {% include 'header.html' %} {{ board.url }} - {{ board.title|e }} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} diff --git a/templates/page.html b/templates/page.html index 816bb72b..296b517e 100644 --- a/templates/page.html +++ b/templates/page.html @@ -8,7 +8,7 @@ {% include 'header.html' %} {{ title }} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} diff --git a/templates/post_thread.html b/templates/post_thread.html index 61146491..72ca2998 100644 --- a/templates/post_thread.html +++ b/templates/post_thread.html @@ -1,7 +1,7 @@ {% filter remove_whitespace %} {# tabs and new lines will be ignored #} -
+
{% if not index %}{% endif %} {% include 'post/fileinfo.html' %} diff --git a/templates/themes/basic/index.html b/templates/themes/basic/index.html index 8cdaf182..9d7a2c41 100644 --- a/templates/themes/basic/index.html +++ b/templates/themes/basic/index.html @@ -11,7 +11,7 @@ {% if config.default_stylesheet.1 != '' %}{% endif %} {% if config.font_awesome %}{% endif %} - + {{ boardlist.top }}

{{ settings.title }}

diff --git a/templates/thread.html b/templates/thread.html index c909f843..425394bb 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -12,7 +12,7 @@ {% include 'header.html' %} {{ board.url }} - {% if config.thread_subject_in_title and thread.subject %}{{ thread.subject }}{% else %}{{ board.title|e }}{% endif %} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} {% if config.url_banner %}{% endif %} -- cgit v1.3 From b6f3d440809f87a803632cefe66a861c8e20bd9b Mon Sep 17 00:00:00 2001 From: 8chan Date: Sun, 9 Nov 2014 23:24:00 -0800 Subject: Go to bottom link --- templates/thread.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/thread.html b/templates/thread.html index 425394bb..7f8668a3 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -32,7 +32,8 @@ {% include 'attention_bar.html' %} - + {{ config.ad.top }} -- cgit v1.3 From aa0d3395b181671907800cd7110c00e35d8a8a13 Mon Sep 17 00:00:00 2001 From: 8chan Date: Sat, 20 Dec 2014 07:02:42 -0800 Subject: Show first 256 chars of body in --- templates/thread.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/thread.html b/templates/thread.html index 7f8668a3..335cd610 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -10,7 +10,7 @@ </script> {% include 'header.html' %} - <title>{{ board.url }} - {% if config.thread_subject_in_title and thread.subject %}{{ thread.subject }}{% else %}{{ board.title|e }}{% endif %} + {{ board.url }} - {% if config.thread_subject_in_title and thread.subject %}{{ thread.subject }}{% else %}{{ thread.body_nomarkup[:256]|e }}{% endif %} {{ boardlist.top }} -- cgit v1.3 From ce62673a2cf1dd4667b75756c80c44cc5746a63b Mon Sep 17 00:00:00 2001 From: 8chan Date: Mon, 16 Feb 2015 16:31:15 -0800 Subject: OpenGraph information in thread pages https://en.wikipedia.org/wiki/Facebook_Platform#Open_Graph_protocol --- templates/thread.html | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/templates/thread.html b/templates/thread.html index 335cd610..9051d8e0 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -10,7 +10,18 @@ {% include 'header.html' %} - {{ board.url }} - {% if config.thread_subject_in_title and thread.subject %}{{ thread.subject }}{% else %}{{ thread.body_nomarkup[:256]|e }}{% endif %} + + {% set meta_subject %}{% if config.thread_subject_in_title and thread.subject %}{{ thread.subject|e }}{% else %}{{ thread.body_nomarkup[:256]|e }}{% endif %}{% endset %} + + + + + + + {% if thread.files.0.thumb %}{% endif %} + + + {{ board.url }} - {{ meta_subject }} {{ boardlist.top }} -- cgit v1.3 From 3f29170f1bebba532c73e4188b15cdc7e4335c15 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 12:23:18 +0200 Subject: debrand 8chan; btw. the previous commit was [SECURITY] i think? --- templates/thread.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/thread.html b/templates/thread.html index 9051d8e0..4314fa5e 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -13,12 +13,12 @@ {% set meta_subject %}{% if config.thread_subject_in_title and thread.subject %}{{ thread.subject|e }}{% else %}{{ thread.body_nomarkup[:256]|e }}{% endif %}{% endset %} - + - - {% if thread.files.0.thumb %}{% endif %} + + {% if thread.files.0.thumb %}{% endif %} {{ board.url }} - {{ meta_subject }} -- cgit v1.3 From cb97029d0d95e54d7e78e46b26b366060fb7e547 Mon Sep 17 00:00:00 2001 From: anonfagola Date: Sun, 28 Sep 2014 10:01:17 -0700 Subject: Update catalog.html Changed title from being - "Catalog /board/" to "/board/ - Catalog" --- templates/themes/catalog/catalog.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/themes/catalog/catalog.html b/templates/themes/catalog/catalog.html index 1f5e8b05..226c00d3 100644 --- a/templates/themes/catalog/catalog.html +++ b/templates/themes/catalog/catalog.html @@ -8,7 +8,7 @@ , board_name = "{{ board }}"; {% include 'header.html' %} - {{ settings.title }} + {{ board }} - Catalog {{ boardlist.top }} -- cgit v1.3 From 632d0a76d0afc5bd7a8cc90e6625a74fe432065a Mon Sep 17 00:00:00 2001 From: 8chan Date: Wed, 31 Dec 2014 22:37:19 -0800 Subject: Display placeholder if no file in catalog/theme.php; czaks: fix the code a bit --- inc/config.php | 2 +- templates/themes/catalog/theme.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/config.php b/inc/config.php index ad659343..21f7b5cc 100644 --- a/inc/config.php +++ b/inc/config.php @@ -766,7 +766,7 @@ // Location of thumbnail to use for spoiler images. $config['spoiler_image'] = 'static/spoiler.png'; // Location of thumbnail to use for deleted images. - // $config['image_deleted'] = 'static/deleted.png'; + $config['image_deleted'] = 'static/deleted.png'; // When a thumbnailed image is going to be the same (in dimension), just copy the entire file and use // that as a thumbnail instead of resizing/redrawing. diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index 34b28e48..c7a8ee95 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -88,6 +88,8 @@ $post['file'] = $config['uri_thumb'] . $files[0]->thumb; } } + } else { + $post['file'] = $config['root'] . $config['image_deleted']; } if (empty($post['image_count'])) $post['image_count'] = 0; -- cgit v1.3 From ae4eb4d3d9c390d4c203a468313154aa59809e59 Mon Sep 17 00:00:00 2001 From: 8chan Date: Mon, 9 Mar 2015 02:31:08 -0700 Subject: RSS --- templates/themes/catalog/index.rss | 19 +++++++++++++++++++ templates/themes/catalog/theme.php | 7 +++++++ 2 files changed, 26 insertions(+) create mode 100644 templates/themes/catalog/index.rss diff --git a/templates/themes/catalog/index.rss b/templates/themes/catalog/index.rss new file mode 100644 index 00000000..c187e45a --- /dev/null +++ b/templates/themes/catalog/index.rss @@ -0,0 +1,19 @@ + + + + /{{ board.uri }}/ - {{ board.title|e }} + https://8ch.net/{{ board.uri }}/ + Live feed of new threads on the 8chan board /{{ board.uri }}/ - {{ board.title|e }}. + + {% for post in recent_posts %} + + {% if post.subject %}{{ post.subject|e }}{% else %}{{ post.body_nomarkup[:256]|e }}{% endif %} + https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html + https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html + https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html + {{ post.pubdate }} + {{ post.body }} ]]> + + {% endfor %} + + diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index c7a8ee95..239d4dff 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -93,6 +93,7 @@ } if (empty($post['image_count'])) $post['image_count'] = 0; + $post['pubdate'] = date('r', $post['time']); $recent_posts[] = $post; } @@ -113,5 +114,11 @@ 'board' => $board_name, 'link' => $config['root'] . $board['dir'] ))); + + file_write($config['dir']['home'] . $board_name . '/index.rss', Element('themes/catalog/index.rss', Array( + 'config' => $config, + 'recent_posts' => $recent_posts, + 'board' => $board + ))); } }; -- cgit v1.3 From 6da7f4d25aed8daf14f705e2acabbac8f755fe01 Mon Sep 17 00:00:00 2001 From: 8chan Date: Sat, 14 Mar 2015 22:03:08 -0700 Subject: No more country flags in --- inc/lib/Twig/Extensions/Extension/Tinyboard.php | 1 + templates/themes/catalog/index.rss | 2 +- templates/thread.html | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/inc/lib/Twig/Extensions/Extension/Tinyboard.php b/inc/lib/Twig/Extensions/Extension/Tinyboard.php index 028db438..3d964c71 100644 --- a/inc/lib/Twig/Extensions/Extension/Tinyboard.php +++ b/inc/lib/Twig/Extensions/Extension/Tinyboard.php @@ -17,6 +17,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension new Twig_SimpleFilter('extension', 'twig_extension_filter'), new Twig_SimpleFilter('sprintf', 'sprintf'), new Twig_SimpleFilter('capcode', 'capcode'), + new Twig_SimpleFilter('remove_modifiers', 'remove_modifiers'), new Twig_SimpleFilter('hasPermission', 'twig_hasPermission_filter'), new Twig_SimpleFilter('date', 'twig_date_filter'), new Twig_SimpleFilter('poster_id', 'poster_id'), diff --git a/templates/themes/catalog/index.rss b/templates/themes/catalog/index.rss index c187e45a..248b494a 100644 --- a/templates/themes/catalog/index.rss +++ b/templates/themes/catalog/index.rss @@ -7,7 +7,7 @@ <atom:link href="https://8ch.net/{{ board.uri }}/index.rss" rel="self" type="application/rss+xml"/> {% for post in recent_posts %} <item> - <title>{% if post.subject %}{{ post.subject|e }}{% else %}{{ post.body_nomarkup[:256]|e }}{% endif %} + {% if post.subject %}{{ post.subject|e }}{% else %}{{ post.body_nomarkup[:256]|remove_modifiers|e }}{% endif %} https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html diff --git a/templates/thread.html b/templates/thread.html index 4314fa5e..2ca81e2e 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -11,7 +11,7 @@ {% include 'header.html' %} - {% set meta_subject %}{% if config.thread_subject_in_title and thread.subject %}{{ thread.subject|e }}{% else %}{{ thread.body_nomarkup[:256]|e }}{% endif %}{% endset %} + {% set meta_subject %}{% if config.thread_subject_in_title and thread.subject %}{{ thread.subject|e }}{% else %}{{ thread.body_nomarkup[:256]|remove_modifiers|e }}{% endif %}{% endset %} -- cgit v1.3 From 7a7574bdcace8ca5526ed10fe675cbbabced6ecd Mon Sep 17 00:00:00 2001 From: 8chan Date: Wed, 24 Dec 2014 06:08:19 -0800 Subject: SECURITY / XSS : ?/edit allowed arbitrary HTML to be added by any user thru addition of 1 This allowed ANY user with ?/edit privilege to also have raw_html regardless of whether they had $config['mod']['rawhtml'] Now, any changes to markup modifiers via ?/edit are not allowed. They are removed at read time, and before write they are removed again and the ones in the database (which should be clean...) are inserted instead. Please immediately apply this patch to your instance if you are running any version of 8chan/infinity. --- inc/functions.php | 6 +++++- inc/mod/pages.php | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index c46807bb..85b61b2e 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1849,7 +1849,11 @@ function extract_modifiers($body) { return $modifiers; } -function markup(&$body, $track_cites = false) { +function remove_modifiers($body) { + return preg_replace('@(.+?)@usm', '', $body); +} + +function markup(&$body, $track_cites = false, $op = false) { global $board, $config, $markup_urls; $modifiers = extract_modifiers($body); diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 8380cfd3..a90fbbd6 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1473,6 +1473,15 @@ function mod_edit_post($board, $edit_raw_html, $postID) { error($config['error']['404']); if (isset($_POST['name'], $_POST['email'], $_POST['subject'], $_POST['body'])) { + // Remove any modifiers they may have put in + $_POST['body'] = remove_modifiers($_POST['body']); + + // Add back modifiers in the original post + $modifiers = extract_modifiers($post['body_nomarkup']); + foreach ($modifiers as $key => $value) { + $_POST['body'] .= "$value"; + } + if ($edit_raw_html) $query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup WHERE `id` = :id', $board)); else @@ -1501,15 +1510,20 @@ function mod_edit_post($board, $edit_raw_html, $postID) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . link_for($post) . '#' . $postID, true, $config['redirect_http']); } else { + // Remove modifiers + $post['body_nomarkup'] = remove_modifiers($post['body_nomarkup']); + + $post['body_nomarkup'] = utf8tohtml($post['body_nomarkup']); + $post['body'] = utf8tohtml($post['body']); if ($config['minify_html']) { - $post['body_nomarkup'] = str_replace("\n", ' ', utf8tohtml($post['body_nomarkup'])); - $post['body'] = str_replace("\n", ' ', utf8tohtml($post['body'])); + $post['body_nomarkup'] = str_replace("\n", ' ', $post['body_nomarkup']); + $post['body'] = str_replace("\n", ' ', $post['body']); $post['body_nomarkup'] = str_replace("\r", '', $post['body_nomarkup']); $post['body'] = str_replace("\r", '', $post['body']); $post['body_nomarkup'] = str_replace("\t", ' ', $post['body_nomarkup']); $post['body'] = str_replace("\t", ' ', $post['body']); } - + mod_page(_('Edit post'), 'mod/edit_post_form.html', array('token' => $security_token, 'board' => $board, 'raw' => $edit_raw_html, 'post' => $post)); } } -- cgit v1.3 From 6644ff666a1c794c6536c895e322dbcb50710134 Mon Sep 17 00:00:00 2001 From: 8n-tech <8n-tech@users.noreply.github.com> Date: Sat, 18 Apr 2015 04:39:50 +1000 Subject: Also improved some CSS and HTML aspects of the thread layout. Signed-off-by: 8n-tech <8n-tech@users.noreply.github.com> --- stylesheets/style.css | 86 ++++++++++++++++++++++++++++++++++++-------- templates/report_delete.html | 21 ++++++----- templates/thread.html | 31 ++++++++++------ 3 files changed, 105 insertions(+), 33 deletions(-) diff --git a/stylesheets/style.css b/stylesheets/style.css index 2c5663bd..bb7c2634 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -468,20 +468,6 @@ hr { clear: left; } -div.boardlist { - color: #89A; - font-size: 9pt; - margin-top: 3px; -} - -div.boardlist.bottom { - margin-top: 20px; -} - -div.boardlist a { - text-decoration: none; -} - div.report { color: #333; } @@ -944,6 +930,78 @@ span.pln { } } +.clearfix { + display: block; + clear: both; + visibility: hidden; + overflow: hidden; + + font-size: 0px; + line-height: 0px; + + box-sizing: border-box; + border: none; + height: 0; + margin: 0; + padding: 0; + width: 100%; + zoom: 1; +} + +/* === SPECIFIC PAGES & FEATURES === */ + +/* Board List */ +div.boardlist { + margin-top: 3px; + + color: #89A; + font-size: 9pt; +} +div.boardlist.bottom { + margin-top: 12px; + clear: both; +} +div.boardlist a { + text-decoration: none; +} + +/* Threads */ +/* Thread Footer */ +#thread-interactions { + margin: 8px 0; + clear: both; +} +#thread-links { + float: left; +} +#thread-links > a { + padding-left: none; + padding-right: 10px; +} +#thread-quick-reply { + display: none; + position: absolute; + left: 50%; + right: 50%; + text-align: center; + width: 100px; + margin-left: -50px; +} +#thread_stats { + float: right; +} + +#post-moderation-fields { + float: right; + text-align: right; +} +#delete-fields { +} +#report-fields { +} + + + /* threadwatcher */ #watchlist { diff --git a/templates/report_delete.html b/templates/report_delete.html index 2fcb7259..e246da9e 100644 --- a/templates/report_delete.html +++ b/templates/report_delete.html @@ -1,13 +1,16 @@ -{% if config.allow_delete %} -
- {% trans %}Delete Post{% endtrans %} [ - ] +
+ {% if config.allow_delete %} +
+ {% trans %}Delete Post{% endtrans %} [ + ] -
-{% endif %} -
- +
+ {% endif %} + +
+ -
+
+
\ No newline at end of file diff --git a/templates/thread.html b/templates/thread.html index 2ca81e2e..1cbb6d20 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -24,6 +24,7 @@ {{ board.url }} - {{ meta_subject }} + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} {% if config.url_banner %}{% endif %} @@ -56,18 +57,28 @@ [{% trans %}Go to bottom{% endtrans %}]
- - {% if mod %}{% endif %} - {{ body }} - {% include 'report_delete.html' %} + + {% if mod %}{% endif %} + + {{ body }} + + + +
- - [{% trans %}Return{% endtrans %}] - [{% trans %}Go to top{% endtrans %}] - [{% trans %}Catalog{% endtrans %}] - - {{ boardlist.bottom }} {{ config.ad.bottom }} -- cgit v1.3 From 33ef1d2123749db43bba372cbc0aa7ab3df4839a Mon Sep 17 00:00:00 2001 From: Bui Date: Sat, 26 Sep 2015 00:17:05 +0900 Subject: add active page classes to body; czaks: go to bottom @ thread: fixes --- templates/index.html | 2 +- templates/page.html | 2 +- templates/themes/catalog/catalog.html | 2 +- templates/thread.html | 8 ++------ 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/templates/index.html b/templates/index.html index 1eea6e6b..d1871fc8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -16,7 +16,7 @@ {% include 'header.html' %} {{ board.url }} - {{ board.title|e }} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} diff --git a/templates/page.html b/templates/page.html index 296b517e..027ed3b9 100644 --- a/templates/page.html +++ b/templates/page.html @@ -8,7 +8,7 @@ {% include 'header.html' %} {{ title }} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} diff --git a/templates/themes/catalog/catalog.html b/templates/themes/catalog/catalog.html index 226c00d3..0626b376 100644 --- a/templates/themes/catalog/catalog.html +++ b/templates/themes/catalog/catalog.html @@ -10,7 +10,7 @@ {% include 'header.html' %} {{ board }} - Catalog - + {{ boardlist.top }}

{{ settings.title }} (/{{ board }}/)

diff --git a/templates/thread.html b/templates/thread.html index 1cbb6d20..2d4ae262 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -23,7 +23,7 @@ {{ board.url }} - {{ meta_subject }} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} @@ -44,8 +44,7 @@ {% include 'attention_bar.html' %} - + {{ config.ad.top }} @@ -53,9 +52,6 @@ {% if config.global_message %}
{{ config.global_message }}
{% endif %}
- [{% trans %}Return{% endtrans %}] - [{% trans %}Go to bottom{% endtrans %}] -
{% if mod %}{% endif %} -- cgit v1.3 From 33ef3f9b013f9848a39df6aaa2ed092a3478fd11 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 14:14:22 +0200 Subject: synchronize catalog_link --- inc/config.php | 4 ++-- templates/thread.html | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/inc/config.php b/inc/config.php index 21f7b5cc..a3087162 100644 --- a/inc/config.php +++ b/inc/config.php @@ -931,8 +931,8 @@ // Show page navigation links at the top as well. $config['page_nav_top'] = false; - // Show "Catalog" link in page navigation. Use with the Catalog theme. - // $config['catalog_link'] = 'catalog.html'; + // Show "Catalog" link in page navigation. Use with the Catalog theme. Set to false to disable. + $config['catalog_link'] = 'catalog.html'; // Board categories. Only used in the "Categories" theme. // $config['categories'] = array( diff --git a/templates/thread.html b/templates/thread.html index 2d4ae262..3ddb5b1f 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -62,7 +62,9 @@ [{% trans %}Return{% endtrans %}] [{% trans %}Go to top{% endtrans %}] - [{% trans %}Catalog{% endtrans %}] + {% if config.catalog_link %} + [{% trans %}Catalog{% endtrans %}] + {% endif %} -- cgit v1.3 From d069a4c9fd9efb995fffa1d705dd201c09b7d13e Mon Sep 17 00:00:00 2001 From: Forkless Date: Mon, 3 Nov 2014 02:28:04 -0600 Subject: Added option for hiding IDs. --- js/forced-anon.js | 72 ++++++++++++++++++++++++++++--------------- stylesheets/style.css | 8 +++++ templates/post/poster_id.html | 4 +-- 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/js/forced-anon.js b/js/forced-anon.js index 11327ec9..26d0cb4f 100644 --- a/js/forced-anon.js +++ b/js/forced-anon.js @@ -18,10 +18,10 @@ if (active_page == 'ukko' || active_page == 'thread' || active_page == 'index' || (window.Options && Options.get_tab('general'))) $(document).ready(function() { var force_anon = function() { - if($(this).children('a.capcode').length == 0) { + if ($(this).children('a.capcode').length == 0) { var id = $(this).parent().children('a.post_no:eq(1)').text(); - if($(this).children('a.email').length != 0) + if ($(this).children('a.email').length != 0) var p = $(this).children('a.email'); else var p = $(this); @@ -29,7 +29,7 @@ $(document).ready(function() { old_info[id] = {'name': p.children('span.name').text(), 'trip': p.children('span.trip').text()}; p.children('span.name').text('Anonymous'); - if(p.children('span.trip').length != 0) + if (p.children('span.trip').length != 0) p.children('span.trip').text(''); } }; @@ -40,44 +40,60 @@ $(document).ready(function() { var disable_fa = function() { $('p.intro label').each(function() { - if($(this).children('a.capcode').length == 0) { + if ($(this).children('a.capcode').length == 0) { var id = $(this).parent().children('a.post_no:eq(1)').text(); if(old_info[id]) { - if($(this).children('a.email').length != 0) + if ($(this).children('a.email').length != 0) var p = $(this).children('a.email'); else var p = $(this); p.children('span.name').text(old_info[id]['name']); - if(p.children('span.trip').length != 0) + if (p.children('span.trip').length != 0) p.children('span.trip').text(old_info[id]['trip']); } } }); }; + var toggle_id = function() { + if (localStorage.hideids == 'true'){ + $(this).addClass('hidden'); + } else { + $(this).removeClass('hidden'); + } + }; + old_info = {}; forced_anon = localStorage['forcedanon'] ? true : false; - var selector, event; - if (window.Options && Options.get_tab('general')) { - selector = '#forced-anon'; - event = 'change'; - Options.extend_tab("general", ""); - } - else { - selector = '#forced-anon'; - event = 'click'; + if (window.Options && Options.get_tab('general')) { + var s1 = '#hide-ids', s2 = '#forced-anon', e = 'change'; + Options.extend_tab("general", ""); + Options.extend_tab("general", ""); + } + else { + var s1 = '#hide-ids', s2 = '#forced-anon', e = 'click'; + $('hr:first').before(''); $('hr:first').before(''); $('div#forced-anon a').text(_('Forced anonymity')+' (' + (forced_anon ? _('enabled') : _('disabled')) + ')'); - } + } + $(s1).on(e, function(e) { + if (!localStorage.hideids || localStorage.hideids == 'false') { + localStorage.hideids = 'true'; + if (window.Options && Options.get_tab('general')) e.target.checked = true; + } else { + localStorage.hideids = 'false'; + if (window.Options && Options.get_tab('general')) e.target.checked = false; + } + $('.poster_id').each(toggle_id); + }); - $(selector).on(event, function() { + $(s2).on(e, function() { forced_anon = !forced_anon; - - if(forced_anon) { + if (forced_anon) { $('div#forced-anon a').text(_('Forced anonymity')+' ('+_('enabled')+')'); localStorage.forcedanon = true; enable_fa(); @@ -86,21 +102,27 @@ $(document).ready(function() { delete localStorage.forcedanon; disable_fa(); } - return false; }); + // initial option setup on script load + if (localStorage.hideids == 'true'){ + if (window.Options && Options.get_tab('general')) $('#hide-ids>input').prop('checked',true); + $('.poster_id').each(toggle_id); + } + if(forced_anon) { enable_fa(); - - if (window.Options && Options.get_tab('general')) { - $('#toggle-locked-threads>input').prop('checked', true); - } + if (window.Options && Options.get_tab('general')) { + $('#toggle-locked-threads>input').prop('checked', true); + } } $(document).on('new_post', function(e, post) { - if(forced_anon) + if (forced_anon) $(post).find('p.intro label').each(force_anon); + if (localStorage.hideids == 'true') + $(post).find('.poster_id').each(toggle_id); }); }); diff --git a/stylesheets/style.css b/stylesheets/style.css index bb7c2634..e54faf93 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -8,6 +8,10 @@ body { padding-right: 4px; } +.hidden { + display:none; +} + a,a:visited { text-decoration: underline; color: #34345C; @@ -905,6 +909,10 @@ pre { cursor: pointer; } +.poster_id::before { + content: " ID: "; +} + pre { /* Better code tags */ max-width:inherit; diff --git a/templates/post/poster_id.html b/templates/post/poster_id.html index 4efc56ff..51f0624f 100644 --- a/templates/post/poster_id.html +++ b/templates/post/poster_id.html @@ -1,7 +1,7 @@ {% if config.poster_ids %} {% if post.thread %} - ID: {{ post.ip|poster_id(post.thread) }} + {{ post.ip|poster_id(post.thread) }} {% else %} - ID: {{ post.ip|poster_id(post.id) }} + {{ post.ip|poster_id(post.id) }} {% endif %} {% endif %} -- cgit v1.3 From 126ee42b9dffe262457177c5dbb7ffa2db170763 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 14:34:42 +0200 Subject: better rules for stripping combined chars, based on 45c0d327619 by @ctrlcctrlv --- inc/functions.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 85b61b2e..d28d018d 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2152,16 +2152,7 @@ function strip_combining_chars($str) { $o = 0; $ord = ordutf8($char, $o); - if ($ord >= 768 && $ord <= 879) - continue; - - if ($ord >= 7616 && $ord <= 7679) - continue; - - if ($ord >= 8400 && $ord <= 8447) - continue; - - if ($ord >= 65056 && $ord <= 65071) + if ( ($ord >= 768 && $ord <= 879) || ($ord >= 1536 && $ord <= 1791) || ($ord >= 3655 && $ord <= 3659) || ($ord >= 7616 && $ord <= 7679) || ($ord >= 8400 && $ord <= 8447) || ($ord >= 65056 && $ord <= 65071)) continue; $str .= $char; -- cgit v1.3 From aa98ca337e327fb1d8e77e124bf49922190246c4 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 14:36:14 +0200 Subject: i think this lump of code deserves a version bump; v5.1.0 here --- install.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install.php b/install.php index 3aa8f56f..c5bbdf36 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); -- cgit v1.3 From 3515fdabe713fc9143a833d0f6a889ce150142aa Mon Sep 17 00:00:00 2001 From: sourcerect Date: Thu, 4 Jun 2015 04:19:04 -0400 Subject: Fix tab freeze when inlining Fixes ctrlcctrlv/infinity#451 --- js/inline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/inline.js b/js/inline.js index 686a540a..e581b79d 100644 --- a/js/inline.js +++ b/js/inline.js @@ -139,7 +139,7 @@ $(document).ready(function() { if (App.options.get('useInlining')) { var assign_inline = function() { - $('.body a[href*="'+location.pathname+'"]:not([rel]):not(.toolong a), .mentioned a') + $('.body a[href*="'+location.pathname+'"]').not('[rel]').not('.toolong > a').add('.mentioned a') .attr('onclick', null)// XXX disable highlightReply .off('click') .click(inline) -- cgit v1.3 From 65ea7b78c50ab3da60a541237f73b13cd2e7e6d8 Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Sun, 10 May 2015 12:51:35 +0800 Subject: Catalog: click to scroll thread --- js/catalog.js | 10 ++++++++++ stylesheets/style.css | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/js/catalog.js b/js/catalog.js index 065fba7b..9adaeaef 100644 --- a/js/catalog.js +++ b/js/catalog.js @@ -35,4 +35,14 @@ if (active_page == 'catalog') $(function(){ if (catalog.image_size !== undefined) { $('#image_size').val(catalog.image_size).trigger('change'); } + + $('div.thread').on('click', function(e) { + if ($(this).css('overflow-y') === 'hidden') { + $(this).css('overflow-y', 'auto'); + $(this).css('width', '100%'); + } else { + $(this).css('overflow-y', 'hidden'); + $(this).css('width', 'auto'); + } + }); }); diff --git a/stylesheets/style.css b/stylesheets/style.css index e54faf93..b36a9de9 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -1033,7 +1033,6 @@ div.boardlist a { #watchlist-toggle, .watchThread, .watchlist-remove, #clearList, #clearGhosts { cursor: pointer; } -<<<<<<< HEAD #youtube-size input { width: 50px; -- cgit v1.3 From 8496b021a959af16682f8e846737020c53848bca Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 14:57:07 +0200 Subject: comment out global reports --- js/fix-report-delete-submit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/fix-report-delete-submit.js b/js/fix-report-delete-submit.js index 2bf7a70b..abed4d06 100644 --- a/js/fix-report-delete-submit.js +++ b/js/fix-report-delete-submit.js @@ -36,7 +36,7 @@ if ($('#delete-fields #password').length) { } Menu.add_item("report_menu", _("Report")); -Menu.add_item("global_report_menu", _("Global report")); +//Menu.add_item("global_report_menu", _("Global report")); Menu.onclick(function(e, $buf) { var ele = e.target.parentElement.parentElement; var $ele = $(ele); -- cgit v1.3 From 6e33de568d21e87f64bc7b890b9438b061aa15a9 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 15:03:53 +0200 Subject: hide-threads.js: add div.file to fields to hide; thanks fpdl --- js/hide-threads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/hide-threads.js b/js/hide-threads.js index 175018ed..47417d39 100644 --- a/js/hide-threads.js +++ b/js/hide-threads.js @@ -36,7 +36,7 @@ $(document).ready(function(){ } } - var fields_to_hide = 'div.post,div.video-container,video,iframe,img:not(.unanimated),canvas,p.fileinfo,a.hide-thread-link,div.new-posts,br'; + var fields_to_hide = 'div.file,div.post,div.video-container,video,iframe,img:not(.unanimated),canvas,p.fileinfo,a.hide-thread-link,div.new-posts,br'; var do_hide_threads = function() { var id = $(this).children('p.intro').children('a.post_no:eq(1)').text(); -- cgit v1.3 From 2fa37278db9ad0e802a2b4c6a54c5d179ce6df35 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 15:09:25 +0200 Subject: boardlist goes before #top --- templates/thread.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/thread.html b/templates/thread.html index 3ddb5b1f..88e6ec6d 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -24,8 +24,8 @@ {{ board.url }} - {{ meta_subject }} - {{ boardlist.top }} + {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} {% if config.url_banner %}{% endif %}
-- cgit v1.3 From b476b660073adbe827836a7e7860b0fb3a061ff0 Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Sun, 23 Aug 2015 09:04:37 +0800 Subject: [BUG] Image reject repost board option now also affects YT embeds --- inc/functions.php | 47 +++++++++++++++++++++++++++++++++++++++++++++++ post.php | 28 +++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index d28d018d..cbe0bc62 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2438,6 +2438,53 @@ function getPostByHashInThread($hash, $thread) { return false; } +function getPostByEmbed($embed) { + global $board, $config; + $matches = array(); + foreach ($config['embedding'] as &$e) { + if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { + $embed = '%'.$matches[1].'%'; + break; + } + } + + if (!isset($embed)) return false; + + $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` LIKE :embed", $board['uri'])); + $query->bindValue(':embed', $embed, PDO::PARAM_STR); + $query->execute() or error(db_error($query)); + + if ($post = $query->fetch(PDO::FETCH_ASSOC)) { + return $post; + } + + return false; +} + +function getPostByEmbedInThread($embed, $thread) { + global $board, $config; + $matches = array(); + foreach ($config['embedding'] as &$e) { + if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { + $embed = '%'.$matches[1].'%'; + break; + } + } + + if (!isset($embed)) return false; + + $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` = :embed AND ( `thread` = :thread OR `id` = :thread )", $board['uri'])); + $query->bindValue(':embed', $embed, PDO::PARAM_STR); + $query->bindValue(':thread', $thread, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + if ($post = $query->fetch(PDO::FETCH_ASSOC)) { + return $post; + } + + return false; +} + function undoImage(array $post) { if (!$post['has_file'] || !isset($post['files'])) return; diff --git a/post.php b/post.php index 3b4f3311..06a9dc74 100644 --- a/post.php +++ b/post.php @@ -294,6 +294,32 @@ if (isset($_POST['delete'])) { if (!isset($post['embed'])) { error($config['error']['invalid_embed']); } + + if ($config['image_reject_repost']) { + if ($p = getPostByEmbed($post['embed'])) { + error(sprintf($config['error']['fileexists'], + ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) . + ($board['dir'] . $config['dir']['res'] . + ($p['thread'] ? + $p['thread'] . '.html#' . $p['id'] + : + $p['id'] . '.html' + )) + )); + } + } else if (!$post['op'] && $config['image_reject_repost_in_thread']) { + if ($p = getPostByEmbedInThread($post['embed'], $post['thread'])) { + error(sprintf($config['error']['fileexistsinthread'], + ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) . + ($board['dir'] . $config['dir']['res'] . + ($p['thread'] ? + $p['thread'] . '.html#' . $p['id'] + : + $p['id'] . '.html' + )) + )); + } + } } if (!hasPermission($config['mod']['bypass_field_disable'], $board['uri'])) { @@ -798,7 +824,7 @@ if (isset($_POST['delete'])) { )); } } - } + } if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { undoImage($post); -- cgit v1.3 From 7831da83fc09ff90a0f99641bde5e2d8c08e84c5 Mon Sep 17 00:00:00 2001 From: 8chan Date: Mon, 16 Feb 2015 16:21:30 -0800 Subject: New event: rebuildpost, allows you to bind events to ?/edit --- inc/functions.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index cbe0bc62..4746b034 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1166,19 +1166,22 @@ function deleteFile($id, $remove_entirely_if_already=true, $file=null) { // rebuild post (markup) function rebuildPost($id) { - global $board; + global $board, $mod; - $query = prepare(sprintf("SELECT `body_nomarkup`, `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); + $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); if ((!$post = $query->fetch(PDO::FETCH_ASSOC)) || !$post['body_nomarkup']) return false; - markup($body = &$post['body_nomarkup']); + markup($post['body'] = &$post['body_nomarkup']); + $post = (object)$post; + event('rebuildpost', $post); + $post = (array)$post; $query = prepare(sprintf("UPDATE ``posts_%s`` SET `body` = :body WHERE `id` = :id", $board['uri'])); - $query->bindValue(':body', $body); + $query->bindValue(':body', $post['body']); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); -- cgit v1.3 From ce3ce4f1b65198083c232897c169313640f23038 Mon Sep 17 00:00:00 2001 From: 8chan Date: Mon, 16 Feb 2015 16:24:54 -0800 Subject: Fix *0 secure tripcodes caused by accidentally feeding + signs to crypt() --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 4746b034..6a34e7a8 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2374,7 +2374,7 @@ function generate_tripcode($name) { if (isset($config['custom_tripcode']["##{$trip}"])) $trip = $config['custom_tripcode']["##{$trip}"]; else - $trip = '!!' . substr(crypt($trip, '_..A.' . substr(base64_encode(sha1($trip . $config['secure_trip_salt'], true)), 0, 4)), -10); + $trip = '!!' . substr(crypt($trip, str_replace('+', '.', '_..A.' . substr(base64_encode(sha1($trip . $config['secure_trip_salt'], true)), 0, 4))), -10); } else { if (isset($config['custom_tripcode']["#{$trip}"])) $trip = $config['custom_tripcode']["#{$trip}"]; -- cgit v1.3 From 6dd1420f91410531bb2dac506f821d959bf74d0a Mon Sep 17 00:00:00 2001 From: 8chan Date: Wed, 18 Feb 2015 16:55:03 -0800 Subject: Add event to quote backlinks --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 6a34e7a8..8f650828 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1952,7 +1952,7 @@ function markup(&$body, $track_cites = false, $op = false) { } if (isset($cited_posts[$cite])) { - $replacement = '' . '>>' . $cite . @@ -2051,7 +2051,7 @@ function markup(&$body, $track_cites = false, $op = false) { $replacement = '' . '>>>/' . $_board . '/' . $cite . ''; -- cgit v1.3 From 7911c374e86ef8fa69c4545867b9d9d3de12d5c6 Mon Sep 17 00:00:00 2001 From: 8chan Date: Wed, 25 Feb 2015 17:21:49 -0800 Subject: Public action logs commit (log.php) Note: In a previous commit, I began making inc/mod/auth.php more modular with the check_login() function. Including it does NOT check mod login by default anymore like it does on vichan. You have to call check_login(). I've finally included it in inc/functions.php. If you have any custom pages that use inc/mod/auth.php, just including functions.php is enough now. =================================== Also: backports 351375185e5 (early 404) --- inc/config.php | 22 +++++++++++++++++----- inc/functions.php | 22 ++++++++++++++++++++-- inc/mod/auth.php | 2 +- inc/mod/pages.php | 36 ++++++++++++++++++++++++++++++++++++ install.sql | 1 + log.php | 24 ++++++++++++++++++++++++ mod.php | 6 +++--- post.php | 5 +++-- templates/mod/log.html | 16 ++++++++++++++-- tools/inc/cli.php | 1 - 10 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 log.php diff --git a/inc/config.php b/inc/config.php index a3087162..b6bc3b69 100644 --- a/inc/config.php +++ b/inc/config.php @@ -511,6 +511,13 @@ // The timeout for the above, in seconds. $config['upload_by_url_timeout'] = 15; + // Enable early 404? With default settings, a thread would 404 if it was to leave page 3, if it had less + // than 3 replies. + $config['early_404'] = false; + + $config['early_404_page'] = 3; + $config['early_404_replies'] = 5; + // A wordfilter (sometimes referred to as just a "filter" or "censor") automatically scans users’ posts // as they are submitted and changes or censors particular words or phrases. @@ -1537,25 +1544,30 @@ /* * ==================== - * Public post search + * Public pages * ==================== */ + + // Public post search settings $config['search'] = array(); // Enable the search form $config['search']['enable'] = false; // Maximal number of queries per IP address per minutes - $config['search']['queries_per_minutes'] = Array(15, 2); + $config['search']['queries_per_minutes'] = Array(15, 2); // Global maximal number of queries per minutes - $config['search']['queries_per_minutes_all'] = Array(50, 2); + $config['search']['queries_per_minutes_all'] = Array(50, 2); // Limit of search results - $config['search']['search_limit'] = 100; + $config['search']['search_limit'] = 100; // Boards for searching - //$config['search']['boards'] = array('a', 'b', 'c', 'd', 'e'); + //$config['search']['boards'] = array('a', 'b', 'c', 'd', 'e'); + + // Enable public logs? 0: NO, 1: YES, 2: YES, but drop names + $config['public_logs'] = 0; /* * ==================== diff --git a/inc/functions.php b/inc/functions.php index 8f650828..378e40b3 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -18,6 +18,7 @@ require_once 'inc/template.php'; require_once 'inc/database.php'; require_once 'inc/events.php'; require_once 'inc/api.php'; +require_once 'inc/mod/auth.php'; require_once 'inc/polyfill.php'; if (!extension_loaded('gettext')) { @@ -524,7 +525,8 @@ function setupBoard($array) { $board = array( 'uri' => $array['uri'], 'title' => $array['title'], - 'subtitle' => $array['subtitle'] + 'subtitle' => $array['subtitle'], + #'indexed' => $array['indexed'], ); // older versions @@ -1270,7 +1272,7 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) { return true; } -function clean() { +function clean($pid = false) { global $board, $config; $offset = round($config['max_pages']*$config['threads_per_page']); @@ -1281,6 +1283,22 @@ function clean() { $query->execute() or error(db_error($query)); while ($post = $query->fetch(PDO::FETCH_ASSOC)) { deletePost($post['id'], false, false); + if ($pid) modLog("Automatically deleting thread #{$post['id']} due to new thread #{$pid}"); + } + + // Bump off threads with X replies earlier, spam prevention method + if ($config['early_404']) { + $offset = round($config['early_404_page']*$config['threads_per_page']); + $query = prepare(sprintf("SELECT `id` AS `thread_id`, (SELECT COUNT(`id`) FROM ``posts_%s`` WHERE `thread` = `thread_id`) AS `reply_count` FROM ``posts_%s`` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC LIMIT :offset, 9001", $board['uri'], $board['uri'])); + $query->bindValue(':offset', $offset, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + if ($post['reply_count'] < $config['early_404_replies']) { + deletePost($post['thread_id'], false, false); + if ($pid) modLog("Automatically deleting thread #{$post['thread_id']} due to new thread #{$pid} (early 404 is set, #{$post['thread_id']} had {$post['reply_count']} replies)"); + } + } } } diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 009e93ad..42f34196 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -130,7 +130,7 @@ function destroyCookies() { function modLog($action, $_board=null) { global $mod, $board, $config; $query = prepare("INSERT INTO ``modlogs`` VALUES (:id, :ip, :board, :time, :text)"); - $query->bindValue(':id', $mod['id'], PDO::PARAM_INT); + $query->bindValue(':id', (isset($mod['id']) ? $mod['id'] : -1), PDO::PARAM_INT); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->bindValue(':time', time(), PDO::PARAM_INT); $query->bindValue(':text', $action); diff --git a/inc/mod/pages.php b/inc/mod/pages.php index a90fbbd6..ca12eaf1 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -698,6 +698,42 @@ function mod_user_log($username, $page_no = 1) { mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'username' => $username)); } +function mod_board_log($board, $page_no = 1, $hide_names = false, $public = false) { + global $config; + + if ($page_no < 1) + error($config['error']['404']); + + if (!hasPermission($config['mod']['mod_board_log'], $board) && !$public) + error($config['error']['noaccess']); + + $query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board ORDER BY `time` DESC LIMIT :offset, :limit"); + $query->bindValue(':board', $board); + $query->bindValue(':limit', $config['mod']['modlog_page'], PDO::PARAM_INT); + $query->bindValue(':offset', ($page_no - 1) * $config['mod']['modlog_page'], PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + $logs = $query->fetchAll(PDO::FETCH_ASSOC); + + if (empty($logs) && $page_no > 1) + error($config['error']['404']); + + if (!hasPermission($config['mod']['show_ip'])) { + // Supports ipv4 only! + foreach ($logs as $i => &$log) { + $log['text'] = preg_replace_callback('/(?:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:<\/a>)?/', function($matches) { + return "xxxx";//less_ip($matches[1]); + }, $log['text']); + } + } + + $query = prepare("SELECT COUNT(*) FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board"); + $query->bindValue(':board', $board); + $query->execute() or error(db_error($query)); + $count = $query->fetchColumn(); + + mod_page(_('Board log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'board' => $board, 'hide_names' => $hide_names, 'public' => $public)); +} + function mod_view_board($boardName, $page_no = 1) { global $config, $mod; diff --git a/install.sql b/install.sql index 7e661450..024cb349 100644 --- a/install.sql +++ b/install.sql @@ -65,6 +65,7 @@ CREATE TABLE IF NOT EXISTS `boards` ( `uri` varchar(58) CHARACTER SET utf8 NOT NULL, `title` tinytext NOT NULL, `subtitle` tinytext, + -- `indexed` boolean default true, PRIMARY KEY (`uri`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; diff --git a/log.php b/log.php new file mode 100644 index 00000000..1a660c4c --- /dev/null +++ b/log.php @@ -0,0 +1,24 @@ + {% if log.username %} - {{ log.username|e }} + {% if hide_names %} + hidden + {% else %} + {% if not mod|hasPermission(config.mod.modlog) %} + {{ log.username|e }} + {% else %} + {{ log.username|e }} + {% endif %} + {% endif %} {% elseif log.mod == -1 %} system {% else %} @@ -44,7 +52,11 @@ {% if count > logs|count %}

{% for i in range(0, (count - 1) / config.mod.modlog_page) %} - [{{ i + 1 }}] + {% if public %} + [{{ i + 1 }}] + {% else %} + [{{ i + 1 }}] + {% endif %} {% endfor %}

{% endif %} diff --git a/tools/inc/cli.php b/tools/inc/cli.php index f3e8824f..95d51573 100644 --- a/tools/inc/cli.php +++ b/tools/inc/cli.php @@ -39,7 +39,6 @@ if(!getenv('TINYBOARD_PATH')) { putenv('TINYBOARD_PATH=' . getcwd()); require 'inc/functions.php'; -require 'inc/mod/auth.php'; $mod = Array( 'id' => -1, -- cgit v1.3 From 95b1e103cb9f349d7d82dca071636d5cde855ead Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Sun, 29 Mar 2015 09:18:14 +0800 Subject: Edit static pages commit --- .gitmodules | 6 ++ inc/config.php | 6 ++ inc/functions.php | 43 ++++++++++++ inc/mod/pages.php | 162 +++++++++++++++++++++++++++++++++++++++++++ install.sql | 20 +++++- mod.php | 18 +++-- templates/mod/edit_page.html | 29 ++++++++ templates/mod/pages.html | 34 +++++++++ tools/import_rules.php | 16 +++++ 9 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 templates/mod/edit_page.html create mode 100644 templates/mod/pages.html create mode 100644 tools/import_rules.php diff --git a/.gitmodules b/.gitmodules index 73f90e0a..df07fdf3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "js/wPaint"] path = js/wPaint url = https://github.com/vichan-devel/wPaint.git + branch = master + +[submodule "inc/lib/parsedown"] + path = inc/lib/parsedown + url = https://github.com/vichan-devel/parsedown + branch = master diff --git a/inc/config.php b/inc/config.php index b6bc3b69..3fdb8d2f 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1498,6 +1498,9 @@ $config['mod']['ban_appeals'] = MOD; // View the recent posts page $config['mod']['recent'] = MOD; + // Create pages + $config['mod']['edit_pages'] = MOD; + $config['pages_max'] = 10; // Config editor permissions $config['mod']['config'] = array(); @@ -1702,3 +1705,6 @@ // Use CAPTCHA for reports? $config['report_captcha'] = false; + + // Allowed HTML tags in ?/edit_pages. + $config['allowed_html'] = 'a[href|title],p,br,li,ol,ul,strong,em,u,h2,b,i,tt,div,img[src|alt|title],hr'; diff --git a/inc/functions.php b/inc/functions.php index 378e40b3..7970d05a 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -20,6 +20,7 @@ require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/mod/auth.php'; require_once 'inc/polyfill.php'; +//require_once 'inc/lib/parsedown/Parsedown.php'; // we don't need that right now, do we? if (!extension_loaded('gettext')) { require_once 'inc/lib/gettext/gettext.inc'; @@ -2739,3 +2740,45 @@ function link_for($post, $page50 = false, $foreignlink = false, $thread = false) return sprintf($tpl, $id, $slug); } + +function prettify_textarea($s){ + return str_replace("\t", ' ', str_replace("\n", ' ', htmlentities($s))); +} + +class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { + public $name = 'NoExternalImages'; + public function filter(&$uri, $c, $context) { + global $config; + $ct = $context->get('CurrentToken'); + + if (!$ct || $ct->name !== 'img') return true; + + if (!isset($uri->host) && !isset($uri->scheme)) return true; + + if (!in_array($uri->scheme . '://' . $uri->host . '/', $config['allowed_offsite_urls'])) { + error('No off-site links in board announcement images.'); + } + + return true; + } +} + +function purify_html($s) { + global $config; + + $c = HTMLPurifier_Config::createDefault(); + $c->set('HTML.Allowed', $config['allowed_html']); + $uri = $c->getDefinition('URI'); + $uri->addFilter(new HTMLPurifier_URIFilter_NoExternalImages(), $c); + $purifier = new HTMLPurifier($c); + $clean_html = $purifier->purify($s); + return $clean_html; +} + +function markdown($s) { + $pd = new Parsedown(); + $pd->setMarkupEscaped(true); + $pd->setimagesEnabled(false); + + return $pd->text($s); +} diff --git a/inc/mod/pages.php b/inc/mod/pages.php index ca12eaf1..328380d8 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -2628,6 +2628,167 @@ function mod_theme_rebuild($theme_name) { )); } +// This needs to be done for `secure` CSRF prevention compatibility, otherwise the $board will be read in as the token if editing global pages. +function delete_page_base($page = '', $board = false) { + global $config, $mod; + + if (empty($board)) + $board = false; + + if (!$board && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $board)) + error($config['error']['noaccess']); + + if ($board !== FALSE && !openBoard($board)) + error($config['error']['noboard']); + + if ($board) { + $query = prepare('DELETE FROM ``pages`` WHERE `board` = :board AND `name` = :name'); + $query->bindValue(':board', ($board ? $board : NULL)); + } else { + $query = prepare('DELETE FROM ``pages`` WHERE `board` IS NULL AND `name` = :name'); + } + $query->bindValue(':name', $page); + $query->execute() or error(db_error($query)); + + header('Location: ?/edit_pages' . ($board ? ('/' . $board) : ''), true, $config['redirect_http']); +} + +function mod_delete_page($page = '') { + delete_page_base($page); +} + +function mod_delete_page_board($page = '', $board = false) { + delete_page_base($page, $board); +} + +function mod_edit_page($id) { + global $config, $mod, $board; + + $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id'); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + $page = $query->fetch(); + + if (!$page) + error(_('Could not find the page you are trying to edit.')); + + if (!$page['board'] && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $page['board'])) + error($config['error']['noaccess']); + + if ($page['board'] && !openBoard($page['board'])) + error($config['error']['noboard']); + + if (isset($_POST['method'], $_POST['content'])) { + $content = $_POST['content']; + $method = $_POST['method']; + $page['type'] = $method; + + if (!in_array($method, array('markdown', 'html', 'infinity'))) + error(_('Unrecognized page markup method.')); + + switch ($method) { + case 'markdown': + $write = markdown($content); + break; + case 'html': + if (hasPermission($config['mod']['rawhtml'])) { + $write = $content; + } else { + $write = purify_html($content); + } + break; + case 'infinity': + $c = $content; + markup($content); + $write = $content; + $content = $c; + } + + if (!isset($write) or !$write) + error(_('Failed to mark up your input for some reason...')); + + $query = prepare('UPDATE ``pages`` SET `type` = :method, `content` = :content WHERE `id` = :id'); + $query->bindValue(':method', $method); + $query->bindValue(':content', $content); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + + $fn = ($board['uri'] ? ($board['uri'] . '/') : '') . $page['name'] . '.html'; + $body = "
$write
"; + $html = Element('page.html', array('config' => $config, 'body' => $body, 'title' => utf8tohtml($page['title']))); + file_write($fn, $html); + } + + if (!isset($content)) { + $query = prepare('SELECT `content` FROM ``pages`` WHERE `id` = :id'); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + $content = $query->fetchColumn(); + } + + mod_page(sprintf(_('Editing static page: %s'), $page['name']), 'mod/edit_page.html', array('page' => $page, 'token' => make_secure_link_token("edit_page/$id"), 'content' => prettify_textarea($content), 'board' => $board)); +} + +function mod_pages($board = false) { + global $config, $mod, $pdo; + + if (empty($board)) + $board = false; + + if (!$board && $mod['boards'][0] !== '*') + error($config['error']['noaccess']); + + if (!hasPermission($config['mod']['edit_pages'], $board)) + error($config['error']['noaccess']); + + if ($board !== FALSE && !openBoard($board)) + error($config['error']['noboard']); + + if ($board) { + $query = prepare('SELECT * FROM ``pages`` WHERE `board` = :board'); + $query->bindValue(':board', $board); + } else { + $query = query('SELECT * FROM ``pages`` WHERE `board` IS NULL'); + } + $query->execute() or error(db_error($query)); + $pages = $query->fetchAll(PDO::FETCH_ASSOC); + + if (isset($_POST['page'])) { + if ($board and sizeof($pages) > $config['pages_max']) + error(sprintf(_('Sorry, this site only allows %d pages per board.'), $config['pages_max'])); + + if (!preg_match('/^[a-z0-9]{1,255}$/', $_POST['page'])) + error(_('Page names must be < 255 chars and may only contain lowercase letters A-Z and digits 1-9.')); + + foreach ($pages as $i => $p) { + if ($_POST['page'] === $p['name']) + error(_('Refusing to create a new page with the same name as an existing one.')); + } + + $title = ($_POST['title'] ? $_POST['title'] : NULL); + + $query = prepare('INSERT INTO ``pages``(board, title, name) VALUES(:board, :title, :name)'); + $query->bindValue(':board', ($board ? $board : NULL)); + $query->bindValue(':title', $title); + $query->bindValue(':name', $_POST['page']); + $query->execute() or error(db_error($query)); + + $pages[] = array('id' => $pdo->lastInsertId(), 'name' => $_POST['page'], 'board' => $board, 'title' => $title); + } + + foreach ($pages as $i => &$p) { + $p['delete_token'] = make_secure_link_token('edit_pages/delete/' . $p['name'] . ($board ? ('/' . $board) : '')); + } + + mod_page(_('Pages'), 'mod/pages.html', array('pages' => $pages, 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), 'board' => $board)); +} + function mod_debug_antispam() { global $pdo, $config; @@ -2744,3 +2905,4 @@ function mod_debug_apc() { mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars)); } + diff --git a/install.sql b/install.sql index 024cb349..720e4ed5 100644 --- a/install.sql +++ b/install.sql @@ -245,7 +245,7 @@ CREATE TABLE IF NOT EXISTS `search_queries` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -- -------------------------------------------------------- @@ -297,6 +297,24 @@ CREATE TABLE IF NOT EXISTS `ban_appeals` ( KEY `ban_id` (`ban_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; +-- -------------------------------------------------------- + +-- +-- Table structure for table `pages` +-- + +CREATE TABLE `pages` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `board` varchar(255) DEFAULT NULL, + `name` varchar(255) NOT NULL, + `title` varchar(255) DEFAULT NULL, + `type` varchar(255) DEFAULT NULL, + `content` text, + PRIMARY KEY (`id`), + UNIQUE KEY `u_pages` (`name`,`board`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + +>>>>>>> 12fa8ec... Edit static pages commit /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/mod.php b/mod.php index a6ff00cf..fbe679ac 100644 --- a/mod.php +++ b/mod.php @@ -33,11 +33,19 @@ $pages = array( '/log' => 'log', // modlog '/log/(\d+)' => 'log', // modlog - '/log:([^/]+)' => 'user_log', // modlog - '/log:([^/]+)/(\d+)' => 'user_log', // modlog - '/news' => 'secure_POST news', // view news - '/news/(\d+)' => 'secure_POST news', // view news - '/news/delete/(\d+)' => 'secure news_delete', // delete from news + '/log:([^/:]+)' => 'user_log', // modlog + '/log:([^/:]+)/(\d+)' => 'user_log', // modlog + '/log:b:([^/]+)' => 'board_log', // modlog + '/log:b:([^/]+)/(\d+)' => 'board_log', // modlog + + '/edit_news' => 'secure_POST news', // view news + '/edit_news/(\d+)' => 'secure_POST news', // view news + '/edit_news/delete/(\d+)' => 'secure news_delete', // delete from news + + '/edit_pages(?:/?(\%b)?)' => 'secure_POST pages', + '/edit_page/(\d+)' => 'secure_POST edit_page', + '/edit_pages/delete/([a-z0-9]+)' => 'secure delete_page', + '/edit_pages/delete/([a-z0-9]+)/(\%b)' => 'secure delete_page_board', '/noticeboard' => 'secure_POST noticeboard', // view noticeboard '/noticeboard/(\d+)' => 'secure_POST noticeboard', // view noticeboard diff --git a/templates/mod/edit_page.html b/templates/mod/edit_page.html new file mode 100644 index 00000000..3d132767 --- /dev/null +++ b/templates/mod/edit_page.html @@ -0,0 +1,29 @@ +
+ + + + + + + +
{% trans %}Markup method{% endtrans %} + {% set allowed_html = config.allowed_html %} + {% trans %}

"markdown" is provided by parsedown. Note: images disabled.

+

"html" allows the following tags:
{{ allowed_html }}

+

"infinity" is the same as what is used in posts.

+

This page will not convert between formats,
choose it once or do the conversion yourself!

{% endtrans %} +
+ +
{% trans %}Page content{% endtrans %} +
+ {% trans %}Page will appear at:{% endtrans %} + {% if board %} {{ config.domain }}/{{ board.uri }}/{{ page.name }}.html + {% else %} {{ config.site }}/{{ page.name }}.html + {% endif %}
+ + +
diff --git a/templates/mod/pages.html b/templates/mod/pages.html new file mode 100644 index 00000000..c2395c02 --- /dev/null +++ b/templates/mod/pages.html @@ -0,0 +1,34 @@ + +
+

+{% if board %} +{% set page_max = config.pages_max %} +{% trans %}This page allows you to create static pages for your board. The limit is {{ page_max }} pages per board. You will still have to link to your pages somewhere in your board, for example in a sticky or in the board's announcement. To make links in the board's announcement, use <a> HTML tags.{% endtrans %} +{% else %} +{% trans %}This page allows you to create static pages for your imageboard.{% endtrans %} +{% endif %} +

{% trans %}Existing pages{% endtrans %}

+{% if pages %} +
+ + +{% for page in pages %} + +{% endfor %} +{% else %} +No pages yet! +{% endif %} +
{% trans %}URL{% endtrans %}{% trans %}Title{% endtrans %}{% trans %}Edit{% endtrans %}{% trans %}Delete{% endtrans %}
{{ page.name }}{{ page.title }}{% trans %}Edit{% endtrans %}{% trans %}Delete{% endtrans %}
+
+
+

{% trans %}Create a new page{% endtrans %}

+
+ + + + +
{% trans %}URL{% endtrans %}{% trans %}Title{% endtrans %}
+ +
+ +
diff --git a/tools/import_rules.php b/tools/import_rules.php new file mode 100644 index 00000000..f583f035 --- /dev/null +++ b/tools/import_rules.php @@ -0,0 +1,16 @@ + $b) { + $rules = @file_get_contents($b.'/rules.txt'); + if ($rules && !empty(trim($rules))) { + $query = prepare('INSERT INTO ``pages``(name, title, type, board, content) VALUES("rules", "Rules", "html", :board, :content)'); + $query->bindValue(':board', $b); + $query->bindValue(':content', $rules); + $query->execute() or error(db_error($query)); + } +} -- cgit v1.3 From d726eaf195a18a35c44fe0b08aa0960e4a79e272 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:07:21 +0200 Subject: we don't have a htmlpurifier yet ;_; --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 7970d05a..845c4cb3 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2745,7 +2745,7 @@ function prettify_textarea($s){ return str_replace("\t", ' ', str_replace("\n", ' ', htmlentities($s))); } -class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { +/*class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { public $name = 'NoExternalImages'; public function filter(&$uri, $c, $context) { global $config; @@ -2761,7 +2761,7 @@ class HTMLPurifier_URIFilter_NoExternalImages extends HTMLPurifier_URIFilter { return true; } -} +}*/ function purify_html($s) { global $config; -- cgit v1.3 From 91c02c3ec41e5c7fa28e70ec4c9b4194ec941620 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:21:30 +0200 Subject: board pages: add a migration --- install.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/install.php b/install.php index c5bbdf36..5a6512ba 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); -- cgit v1.3 From d788131202604a32986c4ba2bfca841c844ff010 Mon Sep 17 00:00:00 2001 From: 8chan Date: Sun, 14 Dec 2014 03:27:26 -0800 Subject: Allow a board called news to exist --- inc/mod/pages.php | 8 ++++---- templates/mod/dashboard.html | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 328380d8..6d1e20b4 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -608,7 +608,7 @@ function mod_news($page_no = 1) { rebuildThemes('news'); - header('Location: ?/news#' . $pdo->lastInsertId(), true, $config['redirect_http']); + header('Location: ?/edit_news#' . $pdo->lastInsertId(), true, $config['redirect_http']); } $query = prepare("SELECT * FROM ``news`` ORDER BY `id` DESC LIMIT :offset, :limit"); @@ -621,14 +621,14 @@ function mod_news($page_no = 1) { error($config['error']['404']); foreach ($news as &$entry) { - $entry['delete_token'] = make_secure_link_token('news/delete/' . $entry['id']); + $entry['delete_token'] = make_secure_link_token('edit_news/delete/' . $entry['id']); } $query = prepare("SELECT COUNT(*) FROM ``news``"); $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('news'))); + mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('edit_news'))); } function mod_news_delete($id) { @@ -643,7 +643,7 @@ function mod_news_delete($id) { modLog('Deleted a news entry'); - header('Location: ?/news', true, $config['redirect_http']); + header('Location: ?/edit_news', true, $config['redirect_http']); } function mod_log($page_no = 1) { diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html index 7e25555b..5c44a522 100644 --- a/templates/mod/dashboard.html +++ b/templates/mod/dashboard.html @@ -62,7 +62,7 @@ {% endif %}
  • {% trans 'View all noticeboard entries' %}
  • {% endif %} -
  • {% trans 'News' %}
  • +
  • {% trans 'News' %}
  • {% trans 'PM inbox' %} -- cgit v1.3 From ab02a4272514410fc053ced9c17a26bc0c8f1cf4 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:27:43 +0200 Subject: maybe we can try to load Parsedown, after all we can silence the error --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 845c4cb3..e9692d56 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -20,7 +20,7 @@ require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/mod/auth.php'; require_once 'inc/polyfill.php'; -//require_once 'inc/lib/parsedown/Parsedown.php'; // we don't need that right now, do we? +@include_once 'inc/lib/parsedown/Parsedown.php'; // fail silently, this isn't a critical piece of code if (!extension_loaded('gettext')) { require_once 'inc/lib/gettext/gettext.inc'; -- cgit v1.3 From 505adffcdca1c1141b7b46f862b7939f2f4acf45 Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Fri, 3 Apr 2015 14:56:28 +0800 Subject: Cyclical threads ♺ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inc/api.php | 1 + inc/config.php | 5 +++++ inc/functions.php | 8 +++++++- inc/mod/pages.php | 22 ++++++++++++++++++++++ install.php | 7 ++++++- mod.php | 1 + post.php | 11 ++++++++++- templates/post/post_controls.html | 8 +++++++- templates/post_thread.html | 13 ++++++++++--- templates/posts.sql | 1 + 10 files changed, 70 insertions(+), 7 deletions(-) diff --git a/inc/api.php b/inc/api.php index b2d8adec..b280c25b 100644 --- a/inc/api.php +++ b/inc/api.php @@ -32,6 +32,7 @@ class Api { 'images' => 'images', 'sticky' => 'sticky', 'locked' => 'locked', + 'cycle' => 'cyclical', 'bump' => 'last_modified', 'embed' => 'embed', ); diff --git a/inc/config.php b/inc/config.php index 3fdb8d2f..82fdc882 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1245,6 +1245,8 @@ $config['mod']['link_bumpunlock'] = '[-Sage]'; $config['mod']['link_editpost'] = '[Edit]'; $config['mod']['link_move'] = '[Move]'; + $config['mod']['link_cycle'] = '[Cycle]'; + $config['mod']['link_uncycle'] = '[-Cycle]'; // Moderator capcodes. $config['capcode'] = ' ## %s'; @@ -1388,6 +1390,9 @@ $config['mod']['deletebyip_global'] = ADMIN; // Sticky a thread $config['mod']['sticky'] = MOD; + // Cycle a thread + $config['mod']['cycle'] = MOD; + $config['cycle_limit'] = &$config['reply_limit']; // Lock a thread $config['mod']['lock'] = MOD; // Post in a locked thread diff --git a/inc/functions.php b/inc/functions.php index e9692d56..1bd1f32f 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1021,7 +1021,7 @@ function insertFloodPost(array $post) { function post(array $post) { global $pdo, $board; - $query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :files, :num_files, :filehash, :password, :ip, :sticky, :locked, 0, :embed, :slug)", $board['uri'])); + $query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :files, :num_files, :filehash, :password, :ip, :sticky, :locked, :cycle, 0, :embed, :slug)", $board['uri'])); // Basic stuff if (!empty($post['subject'])) { @@ -1061,6 +1061,12 @@ function post(array $post) { $query->bindValue(':locked', false, PDO::PARAM_INT); } + if ($post['op'] && $post['mod'] && isset($post['cycle']) && $post['cycle']) { + $query->bindValue(':cycle', true, PDO::PARAM_INT); + } else { + $query->bindValue(':cycle', false, PDO::PARAM_INT); + } + if ($post['mod'] && isset($post['capcode']) && $post['capcode']) { $query->bindValue(':capcode', $post['capcode'], PDO::PARAM_INT); } else { diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 6d1e20b4..303fa3dd 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1089,6 +1089,28 @@ function mod_sticky($board, $unsticky, $post) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } +function mod_cycle($board, $uncycle, $post) { + global $config; + + if (!openBoard($board)) + error($config['error']['noboard']); + + if (!hasPermission($config['mod']['cycle'], $board)) + error($config['error']['noaccess']); + + $query = prepare(sprintf('UPDATE ``posts_%s`` SET `cycle` = :cycle WHERE `id` = :id AND `thread` IS NULL', $board)); + $query->bindValue(':id', $post); + $query->bindValue(':cycle', $uncycle ? 0 : 1); + $query->execute() or error(db_error($query)); + if ($query->rowCount()) { + modLog(($uncycle ? 'Made not cyclical' : 'Made cyclical') . " thread #{$post}"); + buildThread($post); + buildIndex(); + } + + header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); +} + function mod_bumplock($board, $unbumplock, $post) { global $config; diff --git a/install.php b/install.php index 5a6512ba..de4d3dd0 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); diff --git a/mod.php b/mod.php index fbe679ac..0378d55c 100644 --- a/mod.php +++ b/mod.php @@ -82,6 +82,7 @@ $pages = array( '/(\%b)/deletebyip/(\d+)(/global)?' => 'secure deletebyip', // delete all posts by IP address '/(\%b)/(un)?lock/(\d+)' => 'secure lock', // lock thread '/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread + '/(\%b)/(un)?cycle/(\d+)' => 'secure cycle', // cycle thread '/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread '/themes' => 'themes_list', // manage themes diff --git a/post.php b/post.php index 0d3292bb..98aefe05 100644 --- a/post.php +++ b/post.php @@ -265,7 +265,7 @@ if (isset($_POST['delete'])) { //Check if thread exists if (!$post['op']) { - $query = prepare(sprintf("SELECT `sticky`,`locked`,`sage`,`slug` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri'])); + $query = prepare(sprintf("SELECT `sticky`,`locked`,`cycle`,`sage`,`slug` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri'])); $query->bindValue(':id', $post['thread'], PDO::PARAM_INT); $query->execute() or error(db_error()); @@ -867,6 +867,15 @@ if (isset($_POST['delete'])) { $post['slug'] = slugify($post); insertFloodPost($post); + + // Handle cyclical threads + if (!$post['op'] && isset($thread['cycle']) && $thread['cycle']) { + // Query is a bit weird due to "This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" (MariaDB Ver 15.1 Distrib 10.0.17-MariaDB, for Linux (x86_64)) + $query = prepare(sprintf('DELETE FROM ``posts_%s`` WHERE `thread` = :thread AND `id` NOT IN (SELECT `id` FROM (SELECT `id` FROM ``posts_%s`` WHERE `thread` = :thread ORDER BY `id` DESC LIMIT :limit) i)', $board['uri'], $board['uri'])); + $query->bindValue(':thread', $post['thread']); + $query->bindValue(':limit', $config['cycle_limit'], PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + } if (isset($post['antispam_hash'])) { incrementSpamHash($post['antispam_hash']); diff --git a/templates/post/post_controls.html b/templates/post/post_controls.html index 99a8484c..60dc6fc4 100644 --- a/templates/post/post_controls.html +++ b/templates/post/post_controls.html @@ -41,7 +41,6 @@ {% endif %} {% endif %} - {% if mod|hasPermission(config.mod.move, board.uri) %} {% if not post.thread %} {{ config.mod.link_move }}  @@ -49,6 +48,13 @@ {{ config.mod.link_move }}  {% endif %} {% endif %} +{% if mod|hasPermission(config.mod.cycle, board.uri) %} + {% if post.cycle %} + {{ config.mod.link_uncycle }}  + {% else %} + {{ config.mod.link_cycle }}  + {% endif %} +{% endif %} {% if mod|hasPermission(config.mod.editpost, board.uri) %} {{ config.mod.link_editpost }}  {% endif %} diff --git a/templates/post_thread.html b/templates/post_thread.html index 72ca2998..125d5f78 100644 --- a/templates/post_thread.html +++ b/templates/post_thread.html @@ -19,25 +19,32 @@ {{ post.id }} {% if post.sticky %} {% if config.font_awesome %} - + {% else %} Sticky {% endif %} {% endif %} {% if post.locked %} {% if config.font_awesome %} - + {% else %} Locked {% endif %} {% endif %} {% if post.bumplocked and (config.mod.view_bumplock < 0 or (post.mod and post.mod|hasPermission(config.mod.view_bumplock, board.uri))) %} {% if config.font_awesome %} - + {% else %} Bumplocked {% endif %} {% endif %} + {% if post.cycle %} + {% if config.font_awesome %} + + {% else %} + Cyclical + {% endif %} + {% endif %} {% if index %} [{% trans %}Reply{% endtrans %}] {% endif %} diff --git a/templates/posts.sql b/templates/posts.sql index 6b2249ef..070e687b 100644 --- a/templates/posts.sql +++ b/templates/posts.sql @@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS ``posts_{{ board }}`` ( `ip` varchar(39) CHARACTER SET ascii NOT NULL, `sticky` int(1) NOT NULL, `locked` int(1) NOT NULL, + `cycle` int(1) NOT NULL, `sage` int(1) NOT NULL, `embed` text, `slug` varchar(256) DEFAULT NULL, -- cgit v1.3 From a5bd39dc4ab2394db7f87d80a01480b22e4a74e1 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:49:35 +0200 Subject: mod dashboard html: link to page editor --- templates/mod/dashboard.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html index 5c44a522..d13bf7f2 100644 --- a/templates/mod/dashboard.html +++ b/templates/mod/dashboard.html @@ -19,6 +19,9 @@ {% if mod|hasPermission(config.mod.manageboards) %} [{% trans 'edit' %}] {% endif %} + {% if mod|hasPermission(config.mod.edit_pages) %} + [{% trans 'pages' %}] + {% endif %}
  • {% endfor %} @@ -100,6 +103,9 @@ {% if mod|hasPermission(config.mod.modlog) %}
  • {% trans 'Moderation log' %}
  • {% endif %} + {% if mod|hasPermission(config.mod.edit_pages) %} +
  • {% trans 'Global static pages' %}
  • + {% endif %} {% if mod|hasPermission(config.mod.recent) %}
  • {% trans 'Recent posts' %}
  • {% endif %} -- cgit v1.3 From 3571670b98b2e6293e429f3e629510ab9f38e70a Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:51:34 +0200 Subject: fix catalog link someone? --- templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.html b/templates/index.html index d1871fc8..1be614e3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -69,7 +69,7 @@ [{{ page.num }}]{% if loop.last %} {% endif %} {% endfor %} {{ btn.next }} {% if config.catalog_link %} - | Catalog + | {% trans %}Catalog{% endtrans %} {% endif %}
    -- cgit v1.3 From 3f405b3484ae6b621ca1fdacbf20e1ef9f3b553a Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 16:53:28 +0200 Subject: what if IP address contained bad characters? (highly local) --- templates/mod/ban_form.html | 2 +- templates/mod/view_ip.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/mod/ban_form.html b/templates/mod/ban_form.html index 6c7560ad..98cc34b2 100644 --- a/templates/mod/ban_form.html +++ b/templates/mod/ban_form.html @@ -21,7 +21,7 @@ {% if not hide_ip %} - + {% else %} {% trans 'hidden' %} {% endif %} diff --git a/templates/mod/view_ip.html b/templates/mod/view_ip.html index 1c1c7fa6..4bacc7f6 100644 --- a/templates/mod/view_ip.html +++ b/templates/mod/view_ip.html @@ -45,7 +45,7 @@ {% if mod|hasPermission(config.mod.remove_notes) %} - + [{% trans 'remove' %}] -- cgit v1.3 From ce9f9eec252c435bfc1a9d637ae23c5178f9c1e4 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 17:11:12 +0200 Subject: settings dialog refinements: now you can resize it --- stylesheets/style.css | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/stylesheets/style.css b/stylesheets/style.css index b36a9de9..39d1fd04 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -819,10 +819,14 @@ pre { } #options_div { - width: 600px; - height: 320px; + width: 620px; + height: 400px; + resize: both; + overflow: auto; } + + #alert_div { width: 500px; } @@ -853,7 +857,7 @@ pre { #options_tablist { padding: 0px 5px; left: 0px; - width: 70px; + width: 90px; top: 0px; bottom: 0px; height: 100%; @@ -882,11 +886,12 @@ pre { padding: 10px; position: absolute; top: 0px; - bottom: 0px; - left: 81px; + bottom: 10px; + left: 101px; right: 0px; text-align: left; font-size: 12px; + overflow-y: auto; } .options_tab h2 { -- cgit v1.3 From deef54fe13c181181b62f8e7e5ec8953e62a6f72 Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 6 May 2016 18:42:33 +0200 Subject: introduce smart_build_helper --- smart_build.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smart_build.php b/smart_build.php index 31d8110a..e287a31a 100644 --- a/smart_build.php +++ b/smart_build.php @@ -1,8 +1,8 @@ bindValue(':id', $thread); if (!$query->execute() || !$query->fetch(PDO::FETCH_ASSOC) ) { - Cache::set("thread_exists_".$b."_".$thread, "no"); + Cache::set("thread_exists_".$b."_".$thread, "no", 3600); return false; } -- cgit v1.3 From a5e22f6d637365042c53f5e9552e8e8444549d6f Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 02:50:44 +0200 Subject: split route and controller parts from smart build --- inc/controller.php | 108 +++++++++++++++++++++++++++++++++++++ inc/route.php | 62 ++++++++++++++++++++++ smart_build.php | 153 ++++------------------------------------------------- 3 files changed, 179 insertions(+), 144 deletions(-) create mode 100644 inc/controller.php create mode 100644 inc/route.php diff --git a/inc/controller.php b/inc/controller.php new file mode 100644 index 00000000..02e33443 --- /dev/null +++ b/inc/controller.php @@ -0,0 +1,108 @@ + $config['max_pages']) return false; + $config['try_smarter'] = true; + $build_pages = array($page); + buildIndex("skip"); + return true; +} + +function sb_api_board($b, $page = 0) { $page = (int)$page; + return sb_board($b, $page + 1); +} + +function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = (int)$thread; + if ($thread < 1) return false; + + if (!preg_match('/^'.$config['board_regex'].'$/u', $b)) return false; + + if (Cache::get("thread_exists_".$b."_".$thread) == "no") return false; + + $query = prepare(sprintf("SELECT MAX(`id`) AS `max` FROM ``posts_%s``", $b)); + if (!$query->execute()) return false; + + $s = $query->fetch(PDO::FETCH_ASSOC); + $max = $s['max']; + + if ($thread > $max) return false; + + $query = prepare(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL", $b)); + $query->bindValue(':id', $thread); + + if (!$query->execute() || !$query->fetch(PDO::FETCH_ASSOC) ) { + Cache::set("thread_exists_".$b."_".$thread, "no", 3600); + return false; + } + + if ($slugcheck && $config['slugify']) { + global $request; + + $link = link_for(array("id" => $thread), $slugcheck === 50, array("uri" => $b)); + $link = "/".$b."/".$config['dir']['res'].$link; + + if ($link != $request) { + header("Location: $link", true, 301); + die(); + } + } + + if ($slugcheck == 50) { // Should we really generate +50 page? Maybe there are not enough posts anyway + global $request; + $r = str_replace("+50", "", $request); + $r = substr($r, 1); // Cut the slash + + if (file_exists($r)) return false; + } + + if (!openBoard($b)) return false; + buildThread($thread); + return true; +} + +function sb_thread_slugcheck($b, $thread) { + return sb_thread($b, $thread, true); +} +function sb_thread_slugcheck50($b, $thread) { + return sb_thread($b, $thread, 50); +} + +function sb_api($b) { global $config, $build_pages; + if (!openBoard($b)) return false; + $config['try_smarter'] = true; + $build_pages = array(-1); + buildIndex(); + return true; +} + +function sb_ukko() { + rebuildTheme("ukko", "post-thread"); + return true; +} + +function sb_catalog($b) { + if (!openBoard($b)) return false; + + rebuildTheme("catalog", "post-thread", $b); + return true; +} + +function sb_recent() { + rebuildTheme("recent", "post-thread"); + return true; +} + +function sb_sitemap() { + rebuildTheme("sitemap", "all"); + return true; +} + diff --git a/inc/route.php b/inc/route.php new file mode 100644 index 00000000..66602d77 --- /dev/null +++ b/inc/route.php @@ -0,0 +1,62 @@ + $fun) { + $id = '@^' . preg_quote($id, '@') . '$@u'; + + $id = str_replace('%b', '('.$config['board_regex'].')', $id); + $id = str_replace('%d', '([0-9]+)', $id); + $id = str_replace('%s', '[a-zA-Z0-9-]+', $id); + + $matches = null; + + if (preg_match ($id, $request, $matches)) { + array_shift($matches); + + $reached = array($fun, $matches); + + break; + } + } + + return $reached; +} + diff --git a/smart_build.php b/smart_build.php index e287a31a..58596055 100644 --- a/smart_build.php +++ b/smart_build.php @@ -1,5 +1,7 @@ $config['max_pages']) return false; - $config['try_smarter'] = true; - $build_pages = array($page); - buildIndex("skip"); - return true; -} - -function sb_api_board($b, $page = 0) { $page = (int)$page; - return sb_board($b, $page + 1); -} - -function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = (int)$thread; - if ($thread < 1) return false; - - if (!preg_match('/^'.$config['board_regex'].'$/u', $b)) return false; - - if (Cache::get("thread_exists_".$b."_".$thread) == "no") return false; - - $query = prepare(sprintf("SELECT MAX(`id`) AS `max` FROM ``posts_%s``", $b)); - if (!$query->execute()) return false; - - $s = $query->fetch(PDO::FETCH_ASSOC); - $max = $s['max']; - - if ($thread > $max) return false; - - $query = prepare(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL", $b)); - $query->bindValue(':id', $thread); - - if (!$query->execute() || !$query->fetch(PDO::FETCH_ASSOC) ) { - Cache::set("thread_exists_".$b."_".$thread, "no", 3600); - return false; - } - - if ($slugcheck && $config['slugify']) { - global $request; - - $link = link_for(array("id" => $thread), $slugcheck === 50, array("uri" => $b)); - $link = "/".$b."/".$config['dir']['res'].$link; - - if ($link != $request) { - header("Location: $link", true, 301); - die(); - } - } - - if ($slugcheck == 50) { // Should we really generate +50 page? Maybe there are not enough posts anyway - global $request; - $r = str_replace("+50", "", $request); - $r = substr($r, 1); // Cut the slash - - if (file_exists($r)) return false; - } - - if (!openBoard($b)) return false; - buildThread($thread); - return true; -} - -function sb_thread_slugcheck($b, $thread) { - return sb_thread($b, $thread, true); -} -function sb_thread_slugcheck50($b, $thread) { - return sb_thread($b, $thread, 50); -} - -function sb_api($b) { global $config, $build_pages; - if (!openBoard($b)) return false; - $config['try_smarter'] = true; - $build_pages = array(-1); - buildIndex(); - return true; -} - -function sb_ukko() { - rebuildTheme("ukko", "post-thread"); - return true; -} - -function sb_catalog($b) { - if (!openBoard($b)) return false; - - rebuildTheme("catalog", "post-thread", $b); - return true; -} - -function sb_recent() { - rebuildTheme("recent", "post-thread"); - return true; -} - -function sb_sitemap() { - rebuildTheme("sitemap", "all"); - return true; -} - -$entrypoints = array(); - -$entrypoints['/%b/'] = 'sb_board'; -$entrypoints['/%b/'.$config['file_index']] = 'sb_board'; -$entrypoints['/%b/'.$config['file_page']] = 'sb_board'; -$entrypoints['/%b/%d.json'] = 'sb_api_board'; -if ($config['api']['enabled']) { - $entrypoints['/%b/threads.json'] = 'sb_api'; - $entrypoints['/%b/catalog.json'] = 'sb_api'; -} - -$entrypoints['/%b/'.$config['dir']['res'].$config['file_page']] = 'sb_thread_slugcheck'; -$entrypoints['/%b/'.$config['dir']['res'].$config['file_page50']] = 'sb_thread_slugcheck50'; -if ($config['slugify']) { - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page_slug']] = 'sb_thread_slugcheck'; - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page50_slug']] = 'sb_thread_slugcheck50'; -} -if ($config['api']['enabled']) { - $entrypoints['/%b/'.$config['dir']['res'].'%d.json'] = 'sb_thread'; -} - -$entrypoints['/*/'] = 'sb_ukko'; -$entrypoints['/*/index.html'] = 'sb_ukko'; -$entrypoints['/recent.html'] = 'sb_recent'; -$entrypoints['/%b/catalog.html'] = 'sb_catalog'; -$entrypoints['/sitemap.xml'] = 'sb_sitemap'; - -$reached = false; - $request = $_SERVER['REQUEST_URI']; -list($request) = explode('?', $request); - -foreach ($entrypoints as $id => $fun) { - $id = '@^' . preg_quote($id, '@') . '$@u'; - $id = str_replace('%b', '('.$config['board_regex'].')', $id); - $id = str_replace('%d', '([0-9]+)', $id); - $id = str_replace('%s', '[a-zA-Z0-9-]+', $id); +$route = route($request); - $matches = null; - - if (preg_match ($id, $request, $matches)) { - array_shift($matches); - - $reached = call_user_func_array($fun, $matches); - - break; - } +if (!$route) { + $reached = false; +} +else { + list ($fun, $args) = $route; + $reached = call_user_func_array($route); } function die_404() { global $config; -- cgit v1.3 From 644f227ab33f247dc82e0208fe2125d5d002f4f4 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 03:09:20 +0200 Subject: fix "Undefined variable: pid"; thanks fpdl and MrFreeman --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index 98aefe05..4ea08ce3 100644 --- a/post.php +++ b/post.php @@ -955,7 +955,7 @@ if (isset($_POST['delete'])) { $build_pages = range(1, $config['max_pages']); if ($post['op']) - clean($pid); + clean($id); event('post-after', $post); -- cgit v1.3 From b6f0317bde1b6d9a9ac5d81a660865a7aa9ad13d Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 10:54:30 +0200 Subject: advanced build (1/2): a small refactor of index generating procedure; generation strategies --- inc/config.php | 70 ++++++++++++++++++++++--- inc/functions.php | 102 +++++++++++++++++++++++++++---------- inc/route.php | 5 +- smart_build.php | 9 +++- templates/themes/catalog/theme.php | 13 +++-- templates/themes/recent/theme.php | 5 +- templates/themes/sitemap/theme.php | 6 ++- templates/themes/ukko/theme.php | 6 ++- 8 files changed, 169 insertions(+), 47 deletions(-) diff --git a/inc/config.php b/inc/config.php index 82fdc882..91804429 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1203,16 +1203,74 @@ // Try not to build pages when we shouldn't have to. $config['try_smarter'] = true; - // EXPERIMENTAL: Defer static HTML building to a moment, when a given file is actually accessed. - // Warning: This option won't run out of the box. You need to tell your webserver, that a file - // for serving 403 and 404 pages is /smart_build.php. Also, you need to turn off indexes. +/* + * ==================== + * Advanced build + * ==================== + */ + + // Strategies for file generation. Also known as an "advanced build". If you don't have performance + // issues, you can safely ignore that part, because it's hard to configure and won't even work on + // your free webhosting. + // + // A strategy is a function, that given the PHP environment and ($fun, $array) variable pair, returns + // an $action array or false. + // + // $fun - a controller function name, see inc/controller.php. This is named after functions, so that + // we can generate the files in daemon. + // + // $array - arguments to be passed + // + // $action - action to be taken. It's an array, and the first element of it is one of the following: + // * "immediate" - generate the page immediately + // * "defer" - defer page generation to a moment a worker daemon gets to build it (serving a stale + // page in the meantime). The remaining arguments are daemon-specific. Daemon isn't + // implemented yet :DDDD inb4 while(true) { generate(Queue::Get()) }; (which is probably it). + // * "build_on_load" - defer page generation to a moment, when the user actually accesses the page. + // This is a smart_build behaviour. You shouldn't use this one too much, if you + // use it for active boards, the server may choke due to a possible race condition. + // See my blog post: https://engine.vichan.net/blog/res/2.html + // + // So, let's assume we want to build a thread 1324 on board /b/, because a new post appeared there. + // We try the first strategy, giving it arguments: 'sb_thread', array('b', 1324). The strategy will + // now return a value $action, denoting an action to do. If $action is false, we try another strategy. + // + // As I said, configuration isn't easy. + // + // 1. chmod 0777 directories: tmp/locks/ and tmp/queue/. + // 2. serve 403 and 404 requests to go thru smart_build.php + // for nginx, this blog post contains config snippets: https://engine.vichan.net/blog/res/2.html + // 3. disable indexes in your webserver + // 4. launch any number of daemons (eg. twice your number of threads?) using the command: + // $ tools/worker.php + // You don't need to do that step if you are not going to use the "defer" option. + // 5. enable smart_build_helper (see below) + // 6. edit the strategies (see inc/functions.php for the builtin ones). You can use lambdas. I will test + // various ones and include one that works best for me. + $config['generation_strategies'] = array(); + // Add a sane strategy. It forces to immediately generate a page user is about to land on. Otherwise, + // it has no opinion, so it needs a fallback strategy. + $config['generation_strategies'][] = 'strategy_sane'; + // Add an immediate catch-all strategy. This is the default function of imageboards: generate all pages + // on post time. + $config['generation_strategies'][] = 'strategy_immediate'; + // NOT RECOMMENDED: Instead of an all-"immediate" strategy, you can use an all-"build_on_load" one (used + // to be initialized using $config['smart_build']; ) for all pages instead of those to be build + // immediately. A rebuild done in this mode should remove all your static files + // $config['generation_strategies'][1] = 'strategy_smart_build'; + + // Deprecated. Leave it false. See above. $config['smart_build'] = false; - // Smart build related: when a file doesn't exist, where should we redirect? + // Use smart_build.php for dispatching missing requests. It may be useful without smart_build or advanced + // build, for example it will regenerate the missing files. + $config['smart_build_helper'] = true; + + // smart_build.php: when a file doesn't exist, where should we redirect? $config['page_404'] = '/404.html'; - // Smart build related: extra entrypoints. - $config['smart_build_entrypoints'] = array(); + // Extra controller entrypoints. Controller is used only by smart_build and advanced build. + $config['controller_entrypoints'] = array(); /* * ==================== diff --git a/inc/functions.php b/inc/functions.php index 1bd1f32f..2957c680 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1319,7 +1319,8 @@ function thread_find_page($thread) { return floor(($config['threads_per_page'] + $index) / $config['threads_per_page']); } -function index($page, $mod=false) { +// $brief means that we won't need to generate anything yet +function index($page, $mod=false, $brief = false) { global $board, $config, $debug; $body = ''; @@ -1350,6 +1351,7 @@ function index($page, $mod=false) { unset($cached); } } + if (!isset($cached)) { $posts = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE `thread` = :id ORDER BY `id` DESC LIMIT :limit", $board['uri'])); $posts->bindValue(':id', $th['id']); @@ -1389,7 +1391,10 @@ function index($page, $mod=false) { } $threads[] = $thread; - $body .= $thread->build(true); + + if (!$brief) { + $body .= $thread->build(true); + } } if ($config['file_board']) { @@ -1610,27 +1615,28 @@ function checkMute() { function buildIndex($global_api = "yes") { global $board, $config, $build_pages; - if (!$config['smart_build']) { - $pages = getPages(); - if (!$config['try_smarter']) - $antibot = create_antibot($board['uri']); + $catalog_api_action = generation_strategy('sb_api', array($board['uri'])); - if ($config['api']['enabled']) { - $api = new Api(); - $catalog = array(); - } + $pages = null; + $antibot = null; + + if ($config['api']['enabled']) { + $api = new Api(); + $catalog = array(); } for ($page = 1; $page <= $config['max_pages']; $page++) { $filename = $board['dir'] . ($page == 1 ? $config['file_index'] : sprintf($config['file_page'], $page)); $jsonFilename = $board['dir'] . ($page - 1) . '.json'; // pages should start from 0 - if ((!$config['api']['enabled'] || $global_api == "skip" || $config['smart_build']) && $config['try_smarter'] - && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages) ) + $wont_build_this_page = $config['try_smarter'] && isset($build_pages) && !empty($build_pages) && !in_array($page, $build_pages); + + if ((!$config['api']['enabled'] || $global_api == "skip") && $wont_build_this_page) continue; - if (!$config['smart_build']) { - $content = index($page); + $action = generation_strategy('sb_board', array($board['uri'], $page)); + if ($action == 'rebuild' || $catalog_api_action == 'rebuild') { + $content = index($page, false, $wont_build_this_page); if (!$content) break; @@ -1641,17 +1647,21 @@ function buildIndex($global_api = "yes") { file_write($jsonFilename, $json); $catalog[$page-1] = $threads; - } - if ($config['api']['enabled'] && $global_api != "skip" && $config['try_smarter'] && isset($build_pages) - && !empty($build_pages) && !in_array($page, $build_pages) ) - continue; + if ($wont_build_this_page) continue; + } if ($config['try_smarter']) { $antibot = create_antibot($board['uri'], 0 - $page); $content['current_page'] = $page; } + elseif (!$antibot) { + create_antibot($board['uri']); + } $antibot->reset(); + if (!$pages) { + $pages = getPages(); + } $content['pages'] = $pages; $content['pages'][$page-1]['selected'] = true; $content['btn'] = getPageButtons($content['pages']); @@ -1659,13 +1669,14 @@ function buildIndex($global_api = "yes") { file_write($filename, Element('index.html', $content)); } - else { + elseif ($action == 'delete' || $catalog_api_action == 'delete') { file_unlink($filename); file_unlink($jsonFilename); } } - if (!$config['smart_build'] && $page < $config['max_pages']) { + // $action is an action for our last page + if (($catalog_api_action == 'rebuild' || $action == 'rebuild' || $action == 'delete') && $page < $config['max_pages']) { for (;$page<=$config['max_pages'];$page++) { $filename = $board['dir'] . ($page==1 ? $config['file_index'] : sprintf($config['file_page'], $page)); file_unlink($filename); @@ -1679,13 +1690,13 @@ function buildIndex($global_api = "yes") { // json api catalog if ($config['api']['enabled'] && $global_api != "skip") { - if ($config['smart_build']) { + if ($catalog_api_action == 'delete') { $jsonFilename = $board['dir'] . 'catalog.json'; file_unlink($jsonFilename); $jsonFilename = $board['dir'] . 'threads.json'; file_unlink($jsonFilename); } - else { + elseif ($catalog_api_action == 'rebuild') { $json = json_encode($api->translateCatalog($catalog)); $jsonFilename = $board['dir'] . 'catalog.json'; file_write($jsonFilename, $json); @@ -2204,7 +2215,9 @@ function buildThread($id, $return = false, $mod = false) { if ($config['try_smarter'] && !$mod) $build_pages[] = thread_find_page($id); - if (!$config['smart_build'] || $return || $mod) { + $action = generation_strategy('sb_thread', array($board['uri'], $id)); + + if ($action == 'rebuild' || $return || $mod) { $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id`", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); @@ -2239,26 +2252,26 @@ function buildThread($id, $return = false, $mod = false) { )); // json api - if ($config['api']['enabled']) { + if ($config['api']['enabled'] && !$mod) { $api = new Api(); $json = json_encode($api->translateThread($thread)); $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; file_write($jsonFilename, $json); } } - else { + elseif($action == 'delete') { $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; file_unlink($jsonFilename); } - if ($config['smart_build'] && !$return && !$mod) { + if ($action == 'delete' && !$return && !$mod) { $noko50fn = $board['dir'] . $config['dir']['res'] . link_for(array('id' => $id), true); file_unlink($noko50fn); file_unlink($board['dir'] . $config['dir']['res'] . link_for(array('id' => $id))); - } else if ($return) { + } elseif ($return) { return $body; - } else { + } elseif ($action == 'rebuild') { $noko50fn = $board['dir'] . $config['dir']['res'] . link_for($thread, true); if ($hasnoko50 || file_exists($noko50fn)) { buildThread50($id, $return, $mod, $thread, $antibot); @@ -2788,3 +2801,36 @@ function markdown($s) { return $pd->text($s); } + +function generation_strategy($fun, $array=array()) { global $config; + $action = false; + + foreach ($config['generation_strategies'] as $s) { + if ($strategy = $s($fun, $array)) { + break; + } + } + + switch ($strategy[0]) { + case 'immediate': + return 'rebuild'; + case 'defer': + // Ok, it gets interesting here :) + Queue::add(serialize(array('build', $fun, $array))); + return 'ignore'; + case 'build_on_load': + return 'delete'; + } +} + +function strategy_immediate($fun, $array) { + return array('immediate'); +} + +function strategy_smart_build($fun, $array) { + return array('build_on_load'); +} + +function strategy_sane($fun, $array) { global $config; + return false; +} diff --git a/inc/route.php b/inc/route.php index 66602d77..2a5c1732 100644 --- a/inc/route.php +++ b/inc/route.php @@ -7,7 +7,7 @@ defined('TINYBOARD') or exit; -function route($path) { +function route($path) { global $config; $entrypoints = array(); $entrypoints['/%b/'] = 'sb_board'; @@ -33,8 +33,11 @@ function route($path) { $entrypoints['/*/index.html'] = 'sb_ukko'; $entrypoints['/recent.html'] = 'sb_recent'; $entrypoints['/%b/catalog.html'] = 'sb_catalog'; + $entrypoints['/%b/index.rss'] = 'sb_catalog'; $entrypoints['/sitemap.xml'] = 'sb_sitemap'; + $entrypoints = array_merge($entrypoints, $config['controller_entrypoints']); + $reached = false; list($request) = explode('?', $path); diff --git a/smart_build.php b/smart_build.php index 58596055..7ca5fcbf 100644 --- a/smart_build.php +++ b/smart_build.php @@ -3,14 +3,16 @@ require_once("inc/functions.php"); require_once("inc/route.php"); require_once("inc/controller.php"); -if (!$config['smart_build'] && !$config["smart_build_helper"]) { - die('You need to enable $config["smart_build"] or $config["smart_build_helper"]'); +if (!$config["smart_build_helper"]) { + die('You need to enable $config["smart_build_helper"]'); } $config['smart_build'] = false; // Let's disable it, so we can build the page for real +$config['generation_strategies'] = array('strategy_immediate'); function after_open_board() { global $config; $config['smart_build'] = false; + $config['generation_strategies'] = array('strategy_immediate'); }; $request = $_SERVER['REQUEST_URI']; @@ -59,6 +61,9 @@ if ($reached) { elseif (preg_match('/\.xml$/', $request)) { header("Content-Type", "application/xml"); } + elseif (preg_match('/\.rss$/', $request)) { + header("Content-Type", "application/rss+xml"); + } else { header("Content-Type", "text/html; charset=utf-8"); } diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index 239d4dff..4f512c03 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -16,20 +16,25 @@ if ($action == 'all') { foreach ($boards as $board) { $b = new Catalog(); - if ($config['smart_build']) { + + $action = generation_strategy("sb_catalog", array($board)); + if ($action == 'delete') { file_unlink($config['dir']['home'] . $board . '/catalog.html'); + file_unlink($config['dir']['home'] . $board . '/index.rss'); } - else { + elseif ($action == 'rebuild') { $b->build($settings, $board); } } } elseif ($action == 'post-thread' || ($settings['update_on_posts'] && $action == 'post') || ($settings['update_on_posts'] && $action == 'post-delete') && in_array($board, $boards)) { $b = new Catalog(); - if ($config['smart_build']) { + $action = generation_strategy("sb_catalog", array($board)); + if ($action == 'delete') { file_unlink($config['dir']['home'] . $board . '/catalog.html'); + file_unlink($config['dir']['home'] . $board . '/index.rss'); } - else { + elseif ($action == 'rebuild') { $b->build($settings, $board); } } diff --git a/templates/themes/recent/theme.php b/templates/themes/recent/theme.php index f44e2529..95921c35 100644 --- a/templates/themes/recent/theme.php +++ b/templates/themes/recent/theme.php @@ -25,10 +25,11 @@ $this->excluded = explode(' ', $settings['exclude']); if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') { - if ($config['smart_build']) { + $action = generation_strategy('sb_recent', array()); + if ($action == 'delete') { file_unlink($config['dir']['home'] . $settings['html']); } - else { + elseif ($action == 'rebuild') { file_write($config['dir']['home'] . $settings['html'], $this->homepage($settings)); } } diff --git a/templates/themes/sitemap/theme.php b/templates/themes/sitemap/theme.php index 52779d53..6bc035bb 100644 --- a/templates/themes/sitemap/theme.php +++ b/templates/themes/sitemap/theme.php @@ -23,10 +23,12 @@ } } - if ($config['smart_build']) { + $action = generation_strategy('sb_sitemap', array()); + + if ($action == 'delete') { file_unlink($settings['path']); } - else { + elseif ($action == 'rebuild') { $boards = explode(' ', $settings['boards']); $threads = array(); diff --git a/templates/themes/ukko/theme.php b/templates/themes/ukko/theme.php index d6fc303c..e572c467 100644 --- a/templates/themes/ukko/theme.php +++ b/templates/themes/ukko/theme.php @@ -11,10 +11,12 @@ return; } - if ($config['smart_build']) { + $action = generation_strategy('sb_ukko', array()); + + if ($action == 'delete') { file_unlink($settings['uri'] . '/index.html'); } - else { + elseif ($action == 'rebuild') { file_write($settings['uri'] . '/index.html', $ukko->build()); } } -- cgit v1.3 From e2653754750917035b79e62b07d286b3a56cf9ca Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 10:59:36 +0200 Subject: fixup --- inc/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 2957c680..8329e774 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1656,7 +1656,7 @@ function buildIndex($global_api = "yes") { $content['current_page'] = $page; } elseif (!$antibot) { - create_antibot($board['uri']); + $antibot = create_antibot($board['uri']); } $antibot->reset(); if (!$pages) { -- cgit v1.3 From 12e6aba5d4a2965591b3df76e4f6c19e35475700 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 13:20:00 +0200 Subject: (2/2) advanced build. implement a daemon that will build static pages. implement a queue and a lock. fix notice in bans. and it even works! the daemon is basic right now, it could work in a mode that it will defer building certain pages until a certain time. --- inc/bans.php | 2 +- inc/config.php | 9 ++++++++- inc/functions.php | 40 +++++++++++++++++++++++++++++++++++----- smart_build.php | 2 +- tmp/queue/generate/.gitkeep | 0 tools/worker.php | 31 +++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 tmp/queue/generate/.gitkeep create mode 100755 tools/worker.php diff --git a/inc/bans.php b/inc/bans.php index 87e06e28..c468eb64 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -166,7 +166,7 @@ class Bans { if ($ban['post']) { $post = json_decode($ban['post']); - $ban['message'] = $post->body; + $ban['message'] = isset($post->body) ? $post->body : 0; } unset($ban['ipstart'], $ban['ipend'], $ban['post'], $ban['creator']); diff --git a/inc/config.php b/inc/config.php index 91804429..5926eb1d 100644 --- a/inc/config.php +++ b/inc/config.php @@ -103,7 +103,7 @@ /* * ==================== - * Cache settings + * Cache, lock and queue settings * ==================== */ @@ -120,6 +120,7 @@ // $config['cache']['enabled'] = 'apc'; // $config['cache']['enabled'] = 'memcached'; // $config['cache']['enabled'] = 'redis'; + // $config['cache']['enabled'] = 'fs'; // Timeout for cached objects such as posts and HTML. $config['cache']['timeout'] = 60 * 60 * 48; // 48 hours @@ -142,6 +143,12 @@ // (this file will be explicitly loaded during cache hit, but not during cache miss). $config['cache_config'] = false; + // Define a lock driver. + $config['lock']['enabled'] = 'fs'; + + // Define a queue driver. + $config['queue']['enabled'] = 'fs'; // xD + /* * ==================== * Cookie settings diff --git a/inc/functions.php b/inc/functions.php index 8329e774..0cfdb72f 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -19,6 +19,8 @@ require_once 'inc/database.php'; require_once 'inc/events.php'; require_once 'inc/api.php'; require_once 'inc/mod/auth.php'; +require_once 'inc/lock.php'; +require_once 'inc/queue.php'; require_once 'inc/polyfill.php'; @include_once 'inc/lib/parsedown/Parsedown.php'; // fail silently, this isn't a critical piece of code @@ -93,6 +95,8 @@ function loadConfig() { 'db', 'api', 'cache', + 'lock', + 'queue', 'cookies', 'error', 'dir', @@ -1749,7 +1753,6 @@ function buildJavascript() { function checkDNSBL() { global $config; - if (isIPv6()) return; // No IPv6 support yet. @@ -2806,17 +2809,17 @@ function generation_strategy($fun, $array=array()) { global $config; $action = false; foreach ($config['generation_strategies'] as $s) { - if ($strategy = $s($fun, $array)) { + if ($action = $s($fun, $array)) { break; } } - switch ($strategy[0]) { + switch ($action[0]) { case 'immediate': return 'rebuild'; case 'defer': // Ok, it gets interesting here :) - Queue::add(serialize(array('build', $fun, $array))); + get_queue('generate')->push(serialize(array('build', $fun, $array, $action))); return 'ignore'; case 'build_on_load': return 'delete'; @@ -2832,5 +2835,32 @@ function strategy_smart_build($fun, $array) { } function strategy_sane($fun, $array) { global $config; - return false; + // Well, ideally a sane strategy would involve a more stringent checking, + // but let's at least have something to get the ball rolling :^) + + if (php_sapi_name() == 'cli') return false; + else if (isset($_POST['mod']) || isset($_POST['json_response'])) return false; + else if ($fun == 'sb_thread' || ($fun == 'sb_board' && $array[1] == 1)) return array('immediate'); + else return false; +} + +// My first, test strategy. +function strategy_first($fun, $array) { + switch ($fun) { + case 'sb_thread': + return array('defer'); + case 'sb_board': + if ($array[1] > 8) return array('build_on_load'); + else return array('defer'); + case 'sb_api': + return array('defer'); + case 'sb_catalog': + return array('defer'); + case 'sb_recent': + return array('build_on_load'); + case 'sb_sitemap': + return array('build_on_load'); + case 'sb_ukko': + return array('defer'); + } } diff --git a/smart_build.php b/smart_build.php index 7ca5fcbf..cfac446a 100644 --- a/smart_build.php +++ b/smart_build.php @@ -24,7 +24,7 @@ if (!$route) { } else { list ($fun, $args) = $route; - $reached = call_user_func_array($route); + $reached = call_user_func_array($fun, $args); } function die_404() { global $config; diff --git a/tmp/queue/generate/.gitkeep b/tmp/queue/generate/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tools/worker.php b/tools/worker.php new file mode 100755 index 00000000..e19fe1c6 --- /dev/null +++ b/tools/worker.php @@ -0,0 +1,31 @@ +#!/usr/bin/php +pop(2); + foreach ($q as $v) { + list($__, $func, $ary, $action) = unserialize($v); + echo "Starting to generate $func ".implode(" ", $ary)."... "; + + call_user_func_array($func, $ary); + + echo "done!\n"; + } + if (!$q) usleep(20000); // 0.02s +} -- cgit v1.3 From f24e0f98143528c7b2cd5c504899c5492f5d75fd Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 14:01:55 +0200 Subject: optimize out openboard when we don`t need it. a big performance improvement too 🏎 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit also, don't call dnsbl for local ip addresses --- inc/functions.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 0cfdb72f..35be56b9 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -556,14 +556,19 @@ function setupBoard($array) { } function openBoard($uri) { - global $config, $build_pages; + global $config, $build_pages, $board; if ($config['try_smarter']) $build_pages = array(); - $board = getBoardInfo($uri); - if ($board) { - setupBoard($board); + // And what if we don't really need to change a board we have opened? + if (isset ($board) && isset ($board['uri']) && $board['uri'] == $uri) { + return true; + } + + $b = getBoardInfo($uri); + if ($b) { + setupBoard($b); if (function_exists('after_open_board')) { after_open_board(); @@ -1759,6 +1764,9 @@ function checkDNSBL() { if (!isset($_SERVER['REMOTE_ADDR'])) return; // Fix your web server configuration + if (preg_match("/^(::(ffff:)?)?(127\.|192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|0\.|255\.)/", $_SERVER['REMOTE_ADDR'])) + return; // It's pointless to check for local IP addresses in dnsbls, isn't it? + if (in_array($_SERVER['REMOTE_ADDR'], $config['dnsbl_exceptions'])) return; -- cgit v1.3 From bb9aaad899c47e4eec49cbd444573e78d48a146a Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 8 May 2016 15:37:49 +0200 Subject: i forgot about a queue and a lock implementation --- inc/lock.php | 39 +++++++++++++++++++++++++++++++++++++++ inc/queue.php | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 inc/lock.php create mode 100644 inc/queue.php diff --git a/inc/lock.php b/inc/lock.php new file mode 100644 index 00000000..4fb2f5df --- /dev/null +++ b/inc/lock.php @@ -0,0 +1,39 @@ +f = fopen("tmp/locks/$key", "w"); + } + } + + // Get a shared lock + function get($nonblock = false) { global $config; + if ($config['lock']['enabled'] == 'fs') { + $wouldblock = false; + flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) return false; + } + return $this; + } + + // Get an exclusive lock + function get_ex($nonblock = false) { global $config; + if ($config['lock']['enabled'] == 'fs') { + $wouldblock = false; + flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) return false; + } + return $this; + } + + // Free a lock + function free() { global $config; + if ($config['lock']['enabled'] == 'fs') { + flock($this->f, LOCK_UN); + } + return $this; + } +} diff --git a/inc/queue.php b/inc/queue.php new file mode 100644 index 00000000..66305b3b --- /dev/null +++ b/inc/queue.php @@ -0,0 +1,49 @@ +lock = new Lock($key); + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + $this->key = "tmp/queue/$key/"; + } + } + + function push($str) { global $config; + if ($config['queue']['enabled'] == 'fs') { + $this->lock->get_ex(); + file_put_contents($this->key.microtime(true), $str); + $this->lock->free(); + } + return $this; + } + + function pop($n = 1) { global $config; + if ($config['queue']['enabled'] == 'fs') { + $this->lock->get_ex(); + $dir = opendir($this->key); + $paths = array(); + while ($n > 0) { + $path = readdir($dir); + if ($path === FALSE) break; + elseif ($path == '.' || $path == '..') continue; + else { $paths[] = $path; $n--; } + } + $out = array(); + foreach ($paths as $v) { + $out []= file_get_contents($this->key.$v); + unlink($this->key.$v); + } + $this->lock->free(); + return $out; + } + } +} + +// Don't use the constructor. Use the get_queue function. +$queues = array(); + +function get_queue($name) { global $queues; + return $queues[$name] = isset ($queues[$name]) ? $queues[$name] : new Queue($name); +} -- cgit v1.3 From ccc9cff23dfdcfb758d635438b0ff8f9fd476496 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 9 May 2016 10:59:50 +0200 Subject: ukko: post filters and reporting --- js/fix-report-delete-submit.js | 2 +- js/post-filter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/fix-report-delete-submit.js b/js/fix-report-delete-submit.js index abed4d06..61c127ab 100644 --- a/js/fix-report-delete-submit.js +++ b/js/fix-report-delete-submit.js @@ -8,7 +8,7 @@ * */ -if (active_page == 'thread' || active_page == 'index') { +if (active_page == 'thread' || active_page == 'index' || active_page == 'ukko') { $(document).on('menu_ready', function(){ var Menu = window.Menu; diff --git a/js/post-filter.js b/js/post-filter.js index 950f43ab..0b2589f0 100644 --- a/js/post-filter.js +++ b/js/post-filter.js @@ -1,4 +1,4 @@ -if (active_page === 'thread' || active_page === 'index' || active_page === 'catalog') { +if (active_page === 'thread' || active_page === 'index' || active_page === 'catalog' || active_page === 'ukko') { $(document).on('menu_ready', function () { 'use strict'; -- cgit v1.3 From 1cff10fd953373abb546dd93f53acaee2eec19cd Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 9 May 2016 11:08:24 +0200 Subject: ukko & fix-re-de-su: fix reporting and deleting from ukko actually --- js/fix-report-delete-submit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/fix-report-delete-submit.js b/js/fix-report-delete-submit.js index 61c127ab..f5cda9ad 100644 --- a/js/fix-report-delete-submit.js +++ b/js/fix-report-delete-submit.js @@ -20,6 +20,7 @@ if ($('#delete-fields #password').length) { var $ele = $(ele); var threadId = $ele.parent().attr('id').replace('thread_', ''); var postId = $ele.find('.post_no').not('[id]').text(); + var board_name = $ele.parent().data('board'); $buf.find('#delete_post_menu,#delete_file_menu').click(function(e) { e.preventDefault(); @@ -30,6 +31,7 @@ if ($('#delete-fields #password').length) { } else { $('#delete_file').prop('checked', ''); } + $('input[type="hidden"][name="board"]').val(board_name); $('input[name=delete][type=submit]').click(); }); }); @@ -42,6 +44,7 @@ Menu.onclick(function(e, $buf) { var $ele = $(ele); var threadId = $ele.parent().attr('id').replace('thread_', ''); var postId = $ele.find('.post_no').not('[id]').text(); + var board_name = $ele.parent().data('board'); $buf.find('#report_menu,#global_report_menu').click(function(e) { if ($(this).attr('id') === "global_report_menu") { -- cgit v1.3 From 4479fc7681d55afeeed90f9ddc4d892741bd6e53 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 9 May 2016 11:18:27 +0200 Subject: thread-watcher and favorites in catalog and ukko --- js/favorites.js | 2 +- js/thread-watcher.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/js/favorites.js b/js/favorites.js index daf7b732..027dc243 100644 --- a/js/favorites.js +++ b/js/favorites.js @@ -50,7 +50,7 @@ function add_favorites() { $('.boardlist').append(boards); }; -if (active_page == 'thread' || active_page == 'index') { +if (active_page == 'thread' || active_page == 'index' || active_page == 'catalog' || active_page == 'ukko') { $(document).ready(function(){ var favorites = JSON.parse(localStorage.favorites); var is_board_favorite = ~$.inArray(board_name, favorites); diff --git a/js/thread-watcher.js b/js/thread-watcher.js index c31a6e74..0796193b 100644 --- a/js/thread-watcher.js +++ b/js/thread-watcher.js @@ -41,7 +41,7 @@ watchlist.render = function(reset) { } else { //If the watchlist has not yet been rendered, create it. var menuStyle = getComputedStyle($('.boardlist')[0]); - $('form[name="post"]').before( + $((active_page == 'ukko') ? 'hr:first' : (active_page == 'catalog') ? 'body>span:first' : 'form[name="post"]').before( $('
    '+ '
    '+ '['+_('Clear List')+'] '+ @@ -60,6 +60,8 @@ watchlist.render = function(reset) { watchlist.add = function(sel) { var threadName, threadInfo; + var board_name = $(sel).parents('.thread').data('board'); + if (active_page === 'thread') { if ($('.subject').length){ //If a subject is given, use the first 20 characters as the thread name. @@ -70,7 +72,7 @@ watchlist.add = function(sel) { //board name, thread name as defined above, current amount of posts, thread url threadInfo = [board_name, threadName, $('.post').length, location.href]; - } else if (active_page === 'index') { + } else if (active_page === 'index' || active_page === 'ukko') { var postCount; //Figure out the post count. @@ -142,7 +144,7 @@ watchlist.exists = function(sel) { }; $(document).ready(function(){ - if (!(active_page == 'thread' || active_page == 'index')) { + if (!(active_page == 'thread' || active_page == 'index' || active_page == 'catalog' || active_page == 'ukko')) { return; } -- cgit v1.3 From 018dd48a66ec7734bad48105a32a2ae9a5f51017 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 9 May 2016 11:58:46 +0200 Subject: post-filter + ukko fixes --- js/post-filter.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/js/post-filter.js b/js/post-filter.js index 0b2589f0..3f2358e4 100644 --- a/js/post-filter.js +++ b/js/post-filter.js @@ -224,6 +224,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata var $ele = $(ele); var threadId = $ele.parent().attr('id').replace('thread_', ''); + var boardId = $ele.parent().data('board'); var postId = $ele.find('.post_no').not('[id]').text(); if (pageData.hasUID) { var postUid = $ele.find('.poster_id').text(); @@ -244,21 +245,21 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata $buffer.find('#filter-menu-unhide').click(function () { // if hidden due to post id, remove it from blacklist // otherwise just show this post - blacklist.remove.post(pageData.boardId, threadId, postId); + blacklist.remove.post(boardId, threadId, postId); show(ele); }); $buffer.find('#filter-menu-hide').addClass('hidden'); } else { $buffer.find('#filter-menu-unhide').addClass('hidden'); $buffer.find('#filter-menu-hide').click(function () { - blacklist.add.post(pageData.boardId, threadId, postId, false); + blacklist.add.post(boardId, threadId, postId, false); }); } // post id if (!$ele.data('hiddenByPost')) { $buffer.find('#filter-add-post-plus').click(function () { - blacklist.add.post(pageData.boardId, threadId, postId, true); + blacklist.add.post(boardId, threadId, postId, true); }); } else { $buffer.find('#filter-add-post-plus').addClass('hidden'); @@ -267,16 +268,16 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata // UID if (pageData.hasUID && !$ele.data('hiddenByUid')) { $buffer.find('#filter-add-id').click(function () { - blacklist.add.uid(pageData.boardId, threadId, postUid, false); + blacklist.add.uid(boardId, threadId, postUid, false); }); $buffer.find('#filter-add-id-plus').click(function () { - blacklist.add.uid(pageData.boardId, threadId, postUid, true); + blacklist.add.uid(boardId, threadId, postUid, true); }); $buffer.find('#filter-remove-id').addClass('hidden'); } else if (pageData.hasUID) { $buffer.find('#filter-remove-id').click(function () { - blacklist.remove.uid(pageData.boardId, threadId, postUid); + blacklist.remove.uid(boardId, threadId, postUid); }); $buffer.find('#filter-add-id').addClass('hidden'); @@ -350,12 +351,13 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata .click(function() { var postId = $(ele).find('.post_no').not('[id]').text(); var hidden = $(ele).data('hidden'); + var boardId = $(ele).parents('.thread').data('board'); if (hidden) { - blacklist.remove.post(pageData.boardId, threadId, postId, false); + blacklist.remove.post(boardId, threadId, postId, false); $(this).html('[–]'); } else { - blacklist.add.post(pageData.boardId, threadId, postId, false); + blacklist.add.post(boardId, threadId, postId, false); $(this).text('[+]'); } }); @@ -375,7 +377,9 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata var name, trip, uid, subject, comment; var i, length, array, rule, pattern; // temp variables - var boardId = pageData.boardId; + var boardId = $post.data('board'); + if (!boardId) boardId = $post.parents('.thread').data('board'); + var localList = pageData.localList; var noReplyList = pageData.noReplyList; var hasUID = pageData.hasUID; @@ -532,12 +536,13 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata return; var threadId = $thread.attr('id').replace('thread_', ''); + var boardId = $thread.data('board'); var op = $thread.children('.op')[0]; var i, array; // temp variables // add posts to localList and noReplyList - if (typeof list.postFilter[pageData.boardId] != 'undefined' && typeof list.postFilter[pageData.boardId][threadId] != 'undefined') { - array = list.postFilter[pageData.boardId][threadId]; + if (typeof list.postFilter[boardId] != 'undefined' && typeof list.postFilter[boardId][threadId] != 'undefined') { + array = list.postFilter[boardId][threadId]; for (i=0; i Date: Mon, 9 May 2016 13:02:21 +0200 Subject: post-filter & ukko: final solution i think --- js/post-filter.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/post-filter.js b/js/post-filter.js index 3f2358e4..3bf55a51 100644 --- a/js/post-filter.js +++ b/js/post-filter.js @@ -179,7 +179,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata $ele.parent().find('.body, .files, .video-container').not($ele.children('.reply').children()).hide(); // hide thread replies on index view - if (active_page == 'index') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').hide(); + if (active_page == 'index' || active_page == 'ukko') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').hide(); } else { // normal posts $ele.children('.body, .files, .video-container').hide(); @@ -372,6 +372,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata */ function filter(post, threadId, pageData) { var $post = $(post); + var list = getList(); var postId = $post.find('.post_no').not('[id]').text(); var name, trip, uid, subject, comment; @@ -814,9 +815,10 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata var threadId; if ($(post).hasClass('reply')) { - threadId = $(post).parent().attr('id').replace('thread_', ''); + threadId = $(post).parents('.thread').attr('id').replace('thread_', ''); } else { threadId = $(post).attr('id').replace('thread_', ''); + post = $(post).children('.op')[0]; } filter(post, threadId, pageData); -- cgit v1.3 From 52fe9bc873e2950606afefad82cd102ad0a4db5c Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 15 May 2016 15:52:41 +0200 Subject: fix sane_strategy for advanced build. should fix the ajax.js problem. --- inc/functions.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 35be56b9..154386b0 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2843,12 +2843,10 @@ function strategy_smart_build($fun, $array) { } function strategy_sane($fun, $array) { global $config; - // Well, ideally a sane strategy would involve a more stringent checking, - // but let's at least have something to get the ball rolling :^) - if (php_sapi_name() == 'cli') return false; - else if (isset($_POST['mod']) || isset($_POST['json_response'])) return false; - else if ($fun == 'sb_thread' || ($fun == 'sb_board' && $array[1] == 1)) return array('immediate'); + else if (isset($_POST['mod'])) return false; + // Thread needs to be done instantly. Same with a board page, but only if posting a new thread. + else if ($fun == 'sb_thread' || ($fun == 'sb_board' && $array[1] == 1 && isset ($_POST['page']))) return array('immediate'); else return false; } -- cgit v1.3 From d285a796674f170b0e7a71ffa0cc7bbee2d5421a Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 31 May 2016 23:28:55 +1000 Subject: Move the 'Go back and rebuild again' to the top of the rebuilt page so you dont have to scroll --- templates/mod/rebuilt.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/mod/rebuilt.html b/templates/mod/rebuilt.html index 57d16fff..f27cdf56 100644 --- a/templates/mod/rebuilt.html +++ b/templates/mod/rebuilt.html @@ -1,12 +1,12 @@

    {% trans 'Rebuilt' %}

    +

    + {% trans 'Go back and rebuild again' %}. +

      {% for log in logs %}
    • {{ log }}
    • {% endfor %}
    -

    - {% trans 'Go back and rebuild again' %}. -

    -- cgit v1.3 From 94c91db097c034c46f66b27d16eb84a4ea814bb6 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 9 Jun 2016 04:51:05 +0200 Subject: fix news deletion; thanks MrFreeman --- templates/mod/news.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/mod/news.html b/templates/mod/news.html index 510d60ce..57058291 100644 --- a/templates/mod/news.html +++ b/templates/mod/news.html @@ -40,7 +40,7 @@
    {% if mod|hasPermission(config.mod.news_delete) %} - [{% trans 'delete' %}] + [{% trans 'delete' %}] {% endif %}

    -- cgit v1.3 From 8a46c7a0d54864d7f640e1972a23e78018368cc2 Mon Sep 17 00:00:00 2001 From: czaks Date: Thu, 9 Jun 2016 11:08:29 +0200 Subject: tesseract OCR support for spamfilters --- inc/config.php | 13 +++++++++++++ inc/functions.php | 2 +- post.php | 43 ++++++++++++++++++++++++++++++++++++++----- tmp/tesseract/.gitkeep | 0 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tmp/tesseract/.gitkeep diff --git a/inc/config.php b/inc/config.php index 5926eb1d..ebe17572 100644 --- a/inc/config.php +++ b/inc/config.php @@ -824,6 +824,15 @@ // Set this to true if you're using Linux and you can execute `md5sum` binary. $config['gnu_md5'] = false; + // Use Tesseract OCR to retrieve text from images, so you can use it as a spamfilter. + $config['tesseract_ocr'] = false; + + // Tesseract parameters + $config['tesseract_params'] = ''; + + // Tesseract preprocess command + $config['tesseract_preprocess_command'] = 'convert -monochrome %s -'; + // Number of posts in a "View Last X Posts" page $config['noko50_count'] = 50; // Number of posts a thread needs before it gets a "View Last X Posts" page. @@ -1015,6 +1024,10 @@ // Minify Javascript using http://code.google.com/p/minify/. $config['minify_js'] = false; + // Dispatch thumbnail loading and image configuration with JavaScript. It will need a certain javascript + // code to work. + $config['javascript_image_dispatch'] = false; + /* * ==================== * Video embedding diff --git a/inc/functions.php b/inc/functions.php index 154386b0..5a8f6000 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2695,7 +2695,7 @@ function slugify($post) { elseif (isset ($post['body_nomarkup']) && $post['body_nomarkup']) $slug = $post['body_nomarkup']; elseif (isset ($post['body']) && $post['body']) - $slug = strip_html($post['body']); + $slug = strip_tags($post['body']); // Fix UTF-8 first $slug = mb_convert_encoding($slug, "UTF-8", "UTF-8"); diff --git a/post.php b/post.php index 4ea08ce3..bf723e01 100644 --- a/post.php +++ b/post.php @@ -652,14 +652,14 @@ if (isset($_POST['delete'])) { $post['filehash'] = md5($allhashes); } } - + if (!hasPermission($config['mod']['bypass_filters'], $board['uri'])) { - require_once 'inc/filters.php'; - + require_once 'inc/filters.php'; + do_filters($post); } - - if ($post['has_file']) { + + if ($post['has_file']) { foreach ($post['files'] as $key => &$file) { if ($file['is_an_image']) { if ($config['ie_mime_type_detection'] !== false) { @@ -787,6 +787,34 @@ if (isset($_POST['delete'])) { $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } + + if ($config['tesseract_ocr']) { // Let's OCR it! + $fname = $file['tmp_name']; + + if ($file['height'] > 500 || $file['width'] > 500) { + $fname = $file['thumb']; + } + + if ($fname == 'spoiler') { // We don't have that much CPU time, do we? + } + else { + $tmpname = "tmp/tesseract/".rand(0,10000000); + + // Preprocess command is an ImageMagick b/w quantization + $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " . + 'tesseract stdin '.escapeshellarg($tmpname).' '.$config['tesseract_params']); + $tmpname .= ".txt"; + + $value = @file_get_contents($tmpname); + @unlink($tmpname); + + if ($value && trim($value)) { + // This one has an effect, that the body is appended to a post body. So you can write a correct + // spamfilter. + $post['body_nomarkup'] .= "".htmlspecialchars($value).""; + } + } + } if (!isset($dont_copy_file) || !$dont_copy_file) { if (isset($file['file_tmp'])) { @@ -827,6 +855,11 @@ if (isset($_POST['delete'])) { } } + // Do filters again if OCRing + if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri'])) { + do_filters($post); + } + if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { undoImage($post); if ($config['robot_mute']) { diff --git a/tmp/tesseract/.gitkeep b/tmp/tesseract/.gitkeep new file mode 100644 index 00000000..e69de29b -- cgit v1.3 From d2bb4a776f681ad47a8b44bc271c8f0b502ef1f1 Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 17 May 2016 09:53:09 +0200 Subject: fail gracefully on no thumbnail --- inc/functions.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 5a8f6000..e576cf99 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1161,8 +1161,10 @@ function deleteFile($id, $remove_entirely_if_already=true, $file=null) { foreach ($files as $i => $f) { if (($file !== false && $i == $file) || $file === null) { // Delete thumbnail - file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb); - unset($files[$i]->thumb); + if (isset ($f->thumb) && $f->thumb) { + file_unlink($board['dir'] . $config['dir']['thumb'] . $f->thumb); + unset($files[$i]->thumb); + } // Delete file file_unlink($board['dir'] . $config['dir']['img'] . $f->file); -- cgit v1.3 From cdd963e79e0ffc5a7bcd69c9cd79d41c8ad74f52 Mon Sep 17 00:00:00 2001 From: fatchan Date: Wed, 1 Jun 2016 00:20:20 +1000 Subject: fix flag spacing --- templates/post/flag.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/post/flag.html b/templates/post/flag.html index 61622368..481e40ae 100644 --- a/templates/post/flag.html +++ b/templates/post/flag.html @@ -1,5 +1,5 @@ {% if config.display_flags and post.modifiers.flag %} - Date: Wed, 1 Jun 2016 19:51:21 +1000 Subject: CSS hover instead of javascript mouseover. Need to use important because the dark/light text determined by IDToRGB is added inline and takes priority over stylesheets. --- js/id_colors.js | 6 ------ stylesheets/style.css | 4 +++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/js/id_colors.js b/js/id_colors.js index 6335f960..55454476 100644 --- a/js/id_colors.js +++ b/js/id_colors.js @@ -50,12 +50,6 @@ if (active_page == 'thread' || active_page == 'index') { "border-radius": "8px", "color": ft }); - - $(el).mouseover(function() { - $(this).css('color', '#800000'); // how about a CSS :hover rule instead? - }).mouseout(function() { - $(this).css('color', ft); - }); } $(".poster_id").each(function(k, v){ diff --git a/stylesheets/style.css b/stylesheets/style.css index 39d1fd04..7bedfe14 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -913,7 +913,9 @@ pre { .poster_id { cursor: pointer; } - +.poster_id:hover { + color: #800000!important; +} .poster_id::before { content: " ID: "; } -- cgit v1.3 From aa0d92a2b413e268ecf60211cf2132248c3c72ad Mon Sep 17 00:00:00 2001 From: fatchan Date: Wed, 1 Jun 2016 20:35:17 +1000 Subject: Force post-hover.js to show OP's on hover. --- js/post-hover.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/js/post-hover.js b/js/post-hover.js index 0becfb70..780f8cff 100644 --- a/js/post-hover.js +++ b/js/post-hover.js @@ -54,10 +54,7 @@ onready(function(){ hovered_at = {'x': e.pageX, 'y': e.pageY}; var start_hover = function($link) { - if($.contains($post[0], $link[0])) { - // link links to itself or to op; ignore - } - else if($post.is(':visible') && + if ($post.is(':visible') && $post.offset().top >= $(window).scrollTop() && $post.offset().top + $post.height() <= $(window).scrollTop() + $(window).height()) { // post is in view -- cgit v1.3 From f27c26907d37c9d04f848f5594022a79a34624ee Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 7 Jun 2016 23:20:38 +1000 Subject: Remove hardcoded 8chan links in catalog RSS --- templates/themes/catalog/index.rss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/themes/catalog/index.rss b/templates/themes/catalog/index.rss index 248b494a..7add8869 100644 --- a/templates/themes/catalog/index.rss +++ b/templates/themes/catalog/index.rss @@ -2,17 +2,17 @@ /{{ board.uri }}/ - {{ board.title|e }} - https://8ch.net/{{ board.uri }}/ + {{ config.root }}{{ board.uri }}/ Live feed of new threads on the 8chan board /{{ board.uri }}/ - {{ board.title|e }}. - + {% for post in recent_posts %} {% if post.subject %}{{ post.subject|e }}{% else %}{{ post.body_nomarkup[:256]|remove_modifiers|e }}{% endif %} - https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html - https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html - https://8ch.net/{{ board.uri }}/res/{{ post.id }}.html + {{ config.root }}{{ board.uri }}/res/{{ post.id }}.html + {{ config.root }}{{ board.uri }}/res/{{ post.id }}.html + {{ config.root }}{{ board.uri }}/res/{{ post.id }}.html {{ post.pubdate }} - {{ post.body }} ]]> + {{ post.body }} ]]> {% endfor %} -- cgit v1.3 From 4f3cc7f316d18b159ed5aef3878a9d30d1717d4e Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 7 Jun 2016 23:29:19 +1000 Subject: Whoops --- templates/themes/catalog/index.rss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/themes/catalog/index.rss b/templates/themes/catalog/index.rss index 7add8869..e6576ffc 100644 --- a/templates/themes/catalog/index.rss +++ b/templates/themes/catalog/index.rss @@ -3,7 +3,7 @@ /{{ board.uri }}/ - {{ board.title|e }} {{ config.root }}{{ board.uri }}/ - Live feed of new threads on the 8chan board /{{ board.uri }}/ - {{ board.title|e }}. + Live feed of new threads on the board /{{ board.uri }}/ - {{ board.title|e }}. {% for post in recent_posts %} -- cgit v1.3 From f23d11be60dd03039b04320dc0ea83f563332c9c Mon Sep 17 00:00:00 2001 From: Duane Moody Date: Thu, 22 Oct 2015 10:50:59 -0700 Subject: Prevent poster IDs from wordwrapping Poster IDs still linebreak between "ID:" and the ID, this corrects that without having to replace the space inbetween with an   nonbreaking space. --- stylesheets/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/stylesheets/style.css b/stylesheets/style.css index 7bedfe14..abd94973 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -912,6 +912,7 @@ pre { .poster_id { cursor: pointer; + white-space:nowrap; } .poster_id:hover { color: #800000!important; -- cgit v1.3 From c9ef21bff98698902cf5c89b3baf29a17864fd7a Mon Sep 17 00:00:00 2001 From: fatchan Date: Wed, 8 Jun 2016 00:03:50 +1000 Subject: Better solution to prevent ID wrapping. Also no longer text-selectable. Much cleaner. --- stylesheets/style.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/stylesheets/style.css b/stylesheets/style.css index abd94973..23d3ad7d 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -912,7 +912,12 @@ pre { .poster_id { cursor: pointer; - white-space:nowrap; + display: inline-block; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; } .poster_id:hover { color: #800000!important; -- cgit v1.3 From e230f1472cdd9fe5d416196d51e905eac22ce70e Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 10 Jun 2016 12:41:53 +0200 Subject: don`t ocr non-images --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index bf723e01..383154fb 100644 --- a/post.php +++ b/post.php @@ -788,7 +788,7 @@ if (isset($_POST['delete'])) { $file['thumbheight'] = $size[1]; } - if ($config['tesseract_ocr']) { // Let's OCR it! + if ($config['tesseract_ocr'] && $file['thumb'] != 'file') { // Let's OCR it! $fname = $file['tmp_name']; if ($file['height'] > 500 || $file['width'] > 500) { -- cgit v1.3 From 356f46237c3281b09d1fcc1df4e19cb12947063e Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 19 Jun 2016 02:15:49 +0200 Subject: fix install.sql after a bad merge --- install.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/install.sql b/install.sql index 720e4ed5..bf9b92bf 100644 --- a/install.sql +++ b/install.sql @@ -314,7 +314,6 @@ CREATE TABLE `pages` ( UNIQUE KEY `u_pages` (`name`,`board`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ->>>>>>> 12fa8ec... Edit static pages commit /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -- cgit v1.3 From fed9065cf1fe759e20ec10baf0aba1309ee6f8ab Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 19 Jun 2016 02:23:24 +0200 Subject: skip non-image files in recent themes; fixes vichan-devel/vichan#185 --- templates/themes/recent/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/themes/recent/theme.php b/templates/themes/recent/theme.php index 95921c35..a826048f 100644 --- a/templates/themes/recent/theme.php +++ b/templates/themes/recent/theme.php @@ -65,7 +65,7 @@ if (isset($post['files'])) $files = json_decode($post['files']); - if ($files[0]->file == 'deleted') continue; + if ($files[0]->file == 'deleted' || $files[0]->thumb == 'file') continue; // board settings won't be available in the template file, so generate links now $post['link'] = $config['root'] . $board['dir'] . $config['dir']['res'] -- cgit v1.3 From 8f4aa2732900afc837d5154f518dc3a683f58bd1 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 19 Jun 2016 02:40:24 +0200 Subject: fix file-selector.js dependencies --- js/file-selector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/file-selector.js b/js/file-selector.js index 207a5ae9..c2b5381b 100644 --- a/js/file-selector.js +++ b/js/file-selector.js @@ -1,8 +1,9 @@ /* - * file-selector.js - Add support for drag and drop file selection, and paste from clipbboard on supported browsers. + * file-selector.js - Add support for drag and drop file selection, and paste from clipboard on supported browsers. * * Usage: * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/ajax.js'; * $config['additional_javascript'][] = 'js/file-selector.js'; */ function init_file_selector(max_images) { -- cgit v1.3 From 11cecf8452d724b1a61e82292cff1cc16a5403e7 Mon Sep 17 00:00:00 2001 From: czaks Date: Tue, 21 Jun 2016 05:03:44 +0200 Subject: Revert "[BUG] Image reject repost board option now also affects YT embeds" This reverts commit b476b660073adbe827836a7e7860b0fb3a061ff0. --- inc/functions.php | 47 ----------------------------------------------- post.php | 28 +--------------------------- 2 files changed, 1 insertion(+), 74 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index e576cf99..043d4de7 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2492,53 +2492,6 @@ function getPostByHashInThread($hash, $thread) { return false; } -function getPostByEmbed($embed) { - global $board, $config; - $matches = array(); - foreach ($config['embedding'] as &$e) { - if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { - $embed = '%'.$matches[1].'%'; - break; - } - } - - if (!isset($embed)) return false; - - $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` LIKE :embed", $board['uri'])); - $query->bindValue(':embed', $embed, PDO::PARAM_STR); - $query->execute() or error(db_error($query)); - - if ($post = $query->fetch(PDO::FETCH_ASSOC)) { - return $post; - } - - return false; -} - -function getPostByEmbedInThread($embed, $thread) { - global $board, $config; - $matches = array(); - foreach ($config['embedding'] as &$e) { - if (preg_match($e[0], $embed, $matches) && isset($matches[1]) && !empty($matches[1])) { - $embed = '%'.$matches[1].'%'; - break; - } - } - - if (!isset($embed)) return false; - - $query = prepare(sprintf("SELECT `id`,`thread` FROM ``posts_%s`` WHERE `embed` = :embed AND ( `thread` = :thread OR `id` = :thread )", $board['uri'])); - $query->bindValue(':embed', $embed, PDO::PARAM_STR); - $query->bindValue(':thread', $thread, PDO::PARAM_INT); - $query->execute() or error(db_error($query)); - - if ($post = $query->fetch(PDO::FETCH_ASSOC)) { - return $post; - } - - return false; -} - function undoImage(array $post) { if (!$post['has_file'] || !isset($post['files'])) return; diff --git a/post.php b/post.php index 383154fb..aa1b8235 100644 --- a/post.php +++ b/post.php @@ -295,32 +295,6 @@ if (isset($_POST['delete'])) { if (!isset($post['embed'])) { error($config['error']['invalid_embed']); } - - if ($config['image_reject_repost']) { - if ($p = getPostByEmbed($post['embed'])) { - error(sprintf($config['error']['fileexists'], - ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) . - ($board['dir'] . $config['dir']['res'] . - ($p['thread'] ? - $p['thread'] . '.html#' . $p['id'] - : - $p['id'] . '.html' - )) - )); - } - } else if (!$post['op'] && $config['image_reject_repost_in_thread']) { - if ($p = getPostByEmbedInThread($post['embed'], $post['thread'])) { - error(sprintf($config['error']['fileexistsinthread'], - ($post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root']) . - ($board['dir'] . $config['dir']['res'] . - ($p['thread'] ? - $p['thread'] . '.html#' . $p['id'] - : - $p['id'] . '.html' - )) - )); - } - } } if (!hasPermission($config['mod']['bypass_field_disable'], $board['uri'])) { @@ -853,7 +827,7 @@ if (isset($_POST['delete'])) { )); } } - } + } // Do filters again if OCRing if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri'])) { -- cgit v1.3 From a55760299c6777fc841a9990fa9b590b17663bb8 Mon Sep 17 00:00:00 2001 From: nekomiko482 Date: Thu, 7 Jul 2016 12:53:40 +0300 Subject: Fixes incompatibility with BSD's md5 output format. fixes #190 --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index aa1b8235..753d2006 100644 --- a/post.php +++ b/post.php @@ -607,7 +607,7 @@ if (isset($_POST['delete'])) { error($config['error']['nomove']); if ($md5cmd) { - $output = shell_exec_error($md5cmd . " < " . escapeshellarg($upload)); + $output = shell_exec_error($md5cmd . " " . escapeshellarg($upload)); $output = explode(' ', $output); $hash = $output[0]; } -- cgit v1.3 From 8548a4ff703acd5a77e4ce678eeb88e4261a1aa6 Mon Sep 17 00:00:00 2001 From: ptchan-foss Date: Fri, 12 Aug 2016 18:18:54 +0100 Subject: Fixed report syslog message --- post.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/post.php b/post.php index 753d2006..0f58dd4c 100644 --- a/post.php +++ b/post.php @@ -148,15 +148,15 @@ if (isset($_POST['delete'])) { markup($reason); foreach ($report as &$id) { - $query = prepare(sprintf("SELECT `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); + $query = prepare(sprintf("SELECT `id`, `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); - $thread = $query->fetchColumn(); + $post = $query->fetch(PDO::FETCH_ASSOC); if ($config['syslog']) _syslog(LOG_INFO, 'Reported post: ' . - '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($thread ? '#' . $id : '') . + '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') . ' for "' . $reason . '"' ); $query = prepare("INSERT INTO ``reports`` VALUES (NULL, :time, :ip, :board, :post, :reason)"); -- cgit v1.3 From 5e335a8564a76f0449d47f0767b4baa8d2cb9e91 Mon Sep 17 00:00:00 2001 From: czaks Date: Sun, 14 Aug 2016 16:24:17 +0200 Subject: preliminary inbound nntpchan support --- inc/config.php | 17 +++ install.php | 15 ++- install.sql | 19 +++ post.php | 359 +++++++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 322 insertions(+), 88 deletions(-) diff --git a/inc/config.php b/inc/config.php index ebe17572..a8afdf7a 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1689,6 +1689,23 @@ // Example: Adding the pre-markup post body to the API as "com_nomarkup". // $config['api']['extra_fields'] = array('body_nomarkup' => 'com_nomarkup'); +/* + * ================== + * NNTPChan settings + * ================== + */ + + $config['nntpchan'] = array(); + + // Enable NNTPChan integration + $config['nntpchan']['enabled'] = false; + + // NNTP server + $config['nntpchan']['server'] = "localhost:1119"; + + // Global dispatch array. Add your boards to it to enable them. + $config['nntpchan']['dispatch'] = array(); // 'overchan.test' => 'test' + /* * ==================== * Other/uncategorized diff --git a/install.php b/install.php index de4d3dd0..6f1617a8 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ vichan upgrade path. diff --git a/install.sql b/install.sql index bf9b92bf..3b390645 100644 --- a/install.sql +++ b/install.sql @@ -314,6 +314,25 @@ CREATE TABLE `pages` ( UNIQUE KEY `u_pages` (`name`,`board`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; +-- -------------------------------------------------------- + +-- +-- Table structure for table `nntp_references` +-- + +CREATE TABLE `nntp_references` ( + `board` varchar(60) NOT NULL, + `id` int(11) unsigned NOT NULL, + `message_id` varchar(255) CHARACTER SET ascii NOT NULL, + `message_id_digest` varchar(40) CHARACTER SET ascii NOT NULL, + `own` tinyint(1) NOT NULL, + `headers` text, + PRIMARY KEY (`message_id_digest`), + UNIQUE KEY `message_id` (`message_id`), + UNIQUE KEY `u_board_id` (`board`, `id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + + /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/post.php b/post.php index aa1b8235..4af09928 100644 --- a/post.php +++ b/post.php @@ -11,6 +11,160 @@ if ((!isset($_POST['mod']) || !$_POST['mod']) && $config['board_locked']) { error("Board is locked"); } +$dropped_post = false; + +if (isset($_GET['Newsgroups']) && $config['nntpchan']['enabled']) { + $_POST = array(); + $_POST['json_response'] = true; + + $headers = json_encode($_GET); + + if (!isset ($_GET['Message-Id'])) { + if (!isset ($_GET['Message-ID'])) { + error("NNTPChan: No message ID"); + } + else $msgid = $_GET['Message-ID']; + } + else $msgid = $_GET['Message-Id']; + + $groups = preg_split("/,\s*/", $_GET['Newsgroups']); + if (count($groups) != 1) { + error("NNTPChan: Messages can go to only one newsgroup"); + } + $group = $groups[0]; + + if (!isset($config['nntpchan']['dispatch'][$group])) { + error("NNTPChan: We don't synchronize $group"); + } + $xboard = $config['nntpchan']['dispatch'][$group]; + + $ref = null; + if (isset ($_GET['References'])) { + $refs = preg_split("/,\s*/", $_GET['References']); + + if (count($refs) > 1) { + error("NNTPChan: We don't support multiple references"); + } + + $ref = $refs[0]; + + $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id` = :ref"); + $query->bindValue(':ref', $ref); + $query->execute() or error(db_error($query)); + + $ary = $query->fetchAll(PDO::FETCH_ASSOC); + + if (count($ary) == 0) { + error("NNTPChan: We don't have $ref that $msgid references"); + } + + $p_id = $ary[0]['id']; + $p_board = $ary[0]['board']; + + if ($p_board != $xboard) { + error("NNTPChan: Cross board references not allowed. Tried to reference $p_board on $xboard"); + } + + $_POST['thread'] = $p_id; + } + + $date = isset($_GET['Date']) ? strtotime($_GET['Date']) : time(); + + list($ct) = explode('; ', $_GET['Content-Type']); + + $query = prepare("SELECT COUNT(*) AS `c` FROM ``nntp_references`` WHERE `message_id` = :msgid"); + $query->bindValue(":msgid", $msgid); + $query->execute() or error(db_error($query)); + + $a = $query->fetch(PDO::FETCH_ASSOC); + if ($a['c'] > 0) { + error("NNTPChan: We already have this post. Post discarded."); + } + + if ($ct == 'text/plain') { + $content = file_get_contents("php://input"); + } + elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') { + _syslog(LOG_INFO, "MM: Files: ".print_r($GLOBALS, true)); + $content = ''; + + $tmpfiles = $_FILES['attachment']; + foreach ($tmpfiles as $id => $file) { + if ($file['type'] == 'text/plain') { + $content .= file_get_contents($file['tmp_name']); + unset($_FILES['attachment'][$id]); + } + elseif ($file['type'] == 'message/rfc822') { // Signed message, ignore for now + unset($_FILES['attachment'][$id]); + } + else { // A real attachment :^) + } + } + + $_FILES = $_FILES['attachment']; + + + } + else { + error("NNTPChan: Wrong mime type: $ct"); + } + + $_POST['subject'] = isset($_GET['Subject']) ? $_GET['Subject'] : ''; + $_POST['board'] = $xboard; + + if (isset ($_GET['From'])) { + list($name, $mail) = explode(" <", $_GET['From'], 2); + $mail = preg_replace('/>\s+$/', '', $mail); + + $_POST['name'] = $name; + //$_POST['email'] = $mail; + $_POST['email'] = ''; + } + + if (isset ($_GET['X_Sage'])) { + $_POST['email'] = 'sage'; + } + + $content = preg_replace_callback('/>>([0-9a-fA-F]{6,})/', function($id) use ($xboard) { + $id = $id[1]; + + $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id_digest` LIKE :rule"); + $idx = $id . "%"; + $query->bindValue(':rule', $idx); + $query->execute() or error(db_error($query)); + + $ary = $query->fetchAll(PDO::FETCH_ASSOC); + if (count($ary) == 0) { + return ">>>>$id"; + } + else { + $ret = array(); + foreach ($ary as $v) { + if ($v['board'] != $xboard) { + $ret[] = ">>>/".$v['board']."/".$v['id']; + } + else { + $ret[] = ">>".$v['id']; + } + } + return implode($ret, ", "); + } + }, $content); + + $_POST['body'] = $content; + + $dropped_post = array( + 'date' => $date, + 'board' => $xboard, + 'msgid' => $msgid, + 'headers' => $headers, + 'from_nntp' => true, + ); +} +elseif (isset($_GET['Newsgroups'])) { + error("NNTPChan: NNTPChan support is disabled"); +} + if (isset($_POST['delete'])) { // Delete @@ -178,8 +332,8 @@ if (isset($_POST['delete'])) { header('Content-Type: text/json'); echo json_encode(array('success' => true)); } -} elseif (isset($_POST['post'])) { - if (!isset($_POST['body'], $_POST['board'])) +} elseif (isset($_POST['post']) || $dropped_post) { + if (!isset($_POST['body'], $_POST['board']) && !$dropped_post) error($config['error']['bot']); $post = array('board' => $_POST['board'], 'files' => array()); @@ -206,61 +360,67 @@ if (isset($_POST['delete'])) { } else $post['op'] = true; - // Check for CAPTCHA right after opening the board so the "return" link is in there - if ($config['recaptcha']) { - if (!isset($_POST['recaptcha_challenge_field']) || !isset($_POST['recaptcha_response_field'])) - error($config['error']['bot']); - // Check what reCAPTCHA has to say... - $resp = recaptcha_check_answer($config['recaptcha_private'], - $_SERVER['REMOTE_ADDR'], - $_POST['recaptcha_challenge_field'], - $_POST['recaptcha_response_field']); - if (!$resp->is_valid) { - error($config['error']['captcha']); + + if (!$dropped_post) { + // Check for CAPTCHA right after opening the board so the "return" link is in there + if ($config['recaptcha']) { + if (!isset($_POST['recaptcha_challenge_field']) || !isset($_POST['recaptcha_response_field'])) + error($config['error']['bot']); + // Check what reCAPTCHA has to say... + $resp = recaptcha_check_answer($config['recaptcha_private'], + $_SERVER['REMOTE_ADDR'], + $_POST['recaptcha_challenge_field'], + $_POST['recaptcha_response_field']); + if (!$resp->is_valid) { + error($config['error']['captcha']); + } } - } - if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) || - (!$post['op'] && $_POST['post'] == $config['button_reply']))) - error($config['error']['bot']); + if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) || + (!$post['op'] && $_POST['post'] == $config['button_reply']))) + error($config['error']['bot']); - // Check the referrer - if ($config['referer_match'] !== false && - (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER'])))) - error($config['error']['referer']); + // Check the referrer + if ($config['referer_match'] !== false && + (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER'])))) + error($config['error']['referer']); - checkDNSBL(); + checkDNSBL(); - // Check if banned - checkBan($board['uri']); + // Check if banned + checkBan($board['uri']); - if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { - check_login(false); - if (!$mod) { - // Liar. You're not a mod. - error($config['error']['notamod']); - } + if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { + check_login(false); + if (!$mod) { + // Liar. You're not a mod. + error($config['error']['notamod']); + } - $post['sticky'] = $post['op'] && isset($_POST['sticky']); - $post['locked'] = $post['op'] && isset($_POST['lock']); - $post['raw'] = isset($_POST['raw']); + $post['sticky'] = $post['op'] && isset($_POST['sticky']); + $post['locked'] = $post['op'] && isset($_POST['lock']); + $post['raw'] = isset($_POST['raw']); - if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) - error($config['error']['noaccess']); - if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) - error($config['error']['noaccess']); - if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) - error($config['error']['noaccess']); - } + if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) + error($config['error']['noaccess']); + if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) + error($config['error']['noaccess']); + if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) + error($config['error']['noaccess']); + } + + if (!$post['mod']) { + $post['antispam_hash'] = checkSpam(array($board['uri'], isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int)$_POST['page'] : null))); + if ($post['antispam_hash'] === true) + error($config['error']['spam']); + } - if (!$post['mod']) { - $post['antispam_hash'] = checkSpam(array($board['uri'], isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int)$_POST['page'] : null))); - if ($post['antispam_hash'] === true) - error($config['error']['spam']); + if ($config['robot_enable'] && $config['robot_mute']) { + checkMute(); + } } - - if ($config['robot_enable'] && $config['robot_mute']) { - checkMute(); + else { + $mod = $post['mod'] = false; } //Check if thread exists @@ -371,28 +531,36 @@ if (isset($_POST['delete'])) { $post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email'])); $post['body'] = $_POST['body']; $post['password'] = $_POST['password']; - $post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || !empty($_FILES['file']['name']))); + $post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || count($_FILES) > 0)); - if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { - $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); - if ($stripped_whitespace == '') { - error($config['error']['tooshort_body']); + if (!$dropped_post) { + + if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { + $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); + if ($stripped_whitespace == '') { + error($config['error']['tooshort_body']); + } } - } - if (!$post['op']) { - // Check if thread is locked - // but allow mods to post - if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) - error($config['error']['locked']); + if (!$post['op']) { + // Check if thread is locked + // but allow mods to post + if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) + error($config['error']['locked']); - $numposts = numPosts($post['thread']); + $numposts = numPosts($post['thread']); - if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $numposts['replies']) - error($config['error']['reply_hard_limit']); + if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $numposts['replies']) + error($config['error']['reply_hard_limit']); - if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $numposts['images']) - error($config['error']['image_hard_limit']); + if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $numposts['images']) + error($config['error']['image_hard_limit']); + } + } + else { + if (!$post['op']) { + $numposts = numPosts($post['thread']); + } } if ($post['has_file']) { @@ -442,7 +610,7 @@ if (isset($_POST['delete'])) { $trip = generate_tripcode($post['name']); $post['name'] = $trip[0]; - $post['trip'] = isset($trip[1]) ? $trip[1] : ''; + $post['trip'] = isset($trip[1]) ? $trip[1] : ''; // XX: Dropped posts and tripcodes $noko = false; if (strtolower($post['email']) == 'noko') { @@ -477,15 +645,17 @@ if (isset($_POST['delete'])) { if (empty($post['files'])) $post['has_file'] = false; - // Check for a file - if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { - if (!$post['has_file'] && $config['force_image_op']) - error($config['error']['noimage']); - } + if (!$dropped_post) { + // Check for a file + if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { + if (!$post['has_file'] && $config['force_image_op']) + error($config['error']['noimage']); + } - // Check for too many files - if (sizeof($post['files']) > $config['max_images']) - error($config['error']['toomanyimages']); + // Check for too many files + if (sizeof($post['files']) > $config['max_images']) + error($config['error']['toomanyimages']); + } if ($config['strip_combining_chars']) { $post['name'] = strip_combining_chars($post['name']); @@ -494,18 +664,19 @@ if (isset($_POST['delete'])) { $post['body'] = strip_combining_chars($post['body']); } - // Check string lengths - if (mb_strlen($post['name']) > 35) - error(sprintf($config['error']['toolong'], 'name')); - if (mb_strlen($post['email']) > 40) - error(sprintf($config['error']['toolong'], 'email')); - if (mb_strlen($post['subject']) > 100) - error(sprintf($config['error']['toolong'], 'subject')); - if (!$mod && mb_strlen($post['body']) > $config['max_body']) - error($config['error']['toolong_body']); - if (mb_strlen($post['password']) > 20) - error(sprintf($config['error']['toolong'], 'password')); - + if (!$dropped_post) { + // Check string lengths + if (mb_strlen($post['name']) > 35) + error(sprintf($config['error']['toolong'], 'name')); + if (mb_strlen($post['email']) > 40) + error(sprintf($config['error']['toolong'], 'email')); + if (mb_strlen($post['subject']) > 100) + error(sprintf($config['error']['toolong'], 'subject')); + if (!$mod && mb_strlen($post['body']) > $config['max_body']) + error($config['error']['toolong_body']); + if (mb_strlen($post['password']) > 20) + error(sprintf($config['error']['toolong'], 'password')); + } wordfilters($post['body']); $post['body'] = escape_markup_modifiers($post['body']); @@ -514,6 +685,7 @@ if (isset($_POST['delete'])) { $post['body'] .= "\n1"; } + if (!$dropped_post) if (($config['country_flags'] && !$config['allow_no_country']) || ($config['country_flags'] && $config['allow_no_country'] && !isset($_POST['no_country']))) { require 'inc/lib/geoip/geoip.inc'; $gi=geoip\geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD); @@ -555,6 +727,7 @@ if (isset($_POST['delete'])) { $post['body'] .= "\n" . $_POST['tag'] . ""; } + if (!$dropped_post) if ($config['proxy_save'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $proxy = preg_replace("/[^0-9a-fA-F.,: ]/", '', $_SERVER['HTTP_X_FORWARDED_FOR']); $post['body'] .= "\n".$proxy.""; @@ -627,7 +800,7 @@ if (isset($_POST['delete'])) { } } - if (!hasPermission($config['mod']['bypass_filters'], $board['uri'])) { + if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { require_once 'inc/filters.php'; do_filters($post); @@ -830,11 +1003,11 @@ if (isset($_POST['delete'])) { } // Do filters again if OCRing - if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri'])) { + if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { do_filters($post); } - if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { + if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) { undoImage($post); if ($config['robot_mute']) { error(sprintf($config['error']['muted'], mute())); @@ -873,6 +1046,18 @@ if (isset($_POST['delete'])) { $post['id'] = $id = post($post); $post['slug'] = slugify($post); + + if ($dropped_post && $dropped_post['from_nntp']) { + $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES ". + "(:board , :id , :message_id , :message_id_digest , false, :headers)"); + + $query->bindValue(':board', $dropped_post['board']); + $query->bindValue(':id', $id); + $query->bindValue(':message_id', $dropped_post['msgid']); + $query->bindValue(':message_id_digest', sha1($dropped_post['msgid'])); + $query->bindValue(':headers', $dropped_post['headers']); + $query->execute() or error(db_error($query)); + } insertFloodPost($post); // Handle cyclical threads -- cgit v1.3 From a779b96370d3353c8409d64b70c724fa783ad219 Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 15 Aug 2016 00:56:06 +0200 Subject: second iteration of nntpchan implementation --- inc/config.php | 24 +++++++- inc/nntpchan/nntpchan.php | 149 ++++++++++++++++++++++++++++++++++++++++++++++ inc/nntpchan/tests.php | 30 ++++++++++ post.php | 29 +++++++++ 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 inc/nntpchan/nntpchan.php create mode 100644 inc/nntpchan/tests.php diff --git a/inc/config.php b/inc/config.php index a8afdf7a..bfb774a8 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1693,6 +1693,11 @@ * ================== * NNTPChan settings * ================== + */ + +/* + * Please keep in mind that NNTPChan support in vichan isn't finished yet / is in an experimental + * state. Please join #nntpchan on Rizon in order to peer with someone. */ $config['nntpchan'] = array(); @@ -1703,9 +1708,26 @@ // NNTP server $config['nntpchan']['server'] = "localhost:1119"; - // Global dispatch array. Add your boards to it to enable them. + // Global dispatch array. Add your boards to it to enable them. Please make + // sure that this setting is set in a global context. $config['nntpchan']['dispatch'] = array(); // 'overchan.test' => 'test' + // Trusted peer - an IP address of your NNTPChan instance. This peer will have + // increased capabilities, eg.: will evade spamfilter. + $config['nntpchan']['trusted_peer'] = '127.0.0.1'; + + // Salt for message ID generation. Keep it long and secure. + $config['nntpchan']['salt'] = 'change_me+please'; + + // A local message ID domain. Make sure to change it. + $config['nntpchan']['domain'] = 'example.vichan.net'; + + // An NNTPChan group name. + // Please set this setting in your board/config.php, not globally. + $config['nntpchan']['group'] = false; // eg. 'overchan.test' + + + /* * ==================== * Other/uncategorized diff --git a/inc/nntpchan/nntpchan.php b/inc/nntpchan/nntpchan.php new file mode 100644 index 00000000..25215e94 --- /dev/null +++ b/inc/nntpchan/nntpchan.php @@ -0,0 +1,149 @@ +"; +} + + +function gen_nntp($headers, $files) { + if (count($files) == 0) { + } + else if (count($files) == 1 && $files[0]['type'] == 'text/plain') { + $content = $files[0]['text'] . "\r\n"; + $headers['Content-Type'] = "text/plain; charset=UTF-8"; + } + else { + $boundary = sha1($headers['Message-Id']); + $content = ""; + $headers['Content-Type'] = "multipart/mixed; boundary=$boundary"; + foreach ($files as $file) { + $content .= "--$boundary\r\n"; + if (isset($file['name'])) { + $file['name'] = preg_replace('/[\r\n\0"]/', '', $file['name']); + $content .= "Content-Disposition: form-data; filename=\"$file[name]\"; name=\"attachment\"\r\n"; + } + $type = explode('/', $file['type'])[0]; + if ($type == 'text') { + $file['type'] .= '; charset=UTF-8'; + } + $content .= "Content-Type: $file[type]\r\n"; + if ($type != 'text' && $type != 'message') { + $file['text'] = base64_encode($file['text']); + $content .= "Content-Transfer-Encoding: base64\r\n"; + } + $content .= "\r\n"; + $content .= $file['text']; + $content .= "\r\n"; + } + $content .= "--$boundary--\r\n"; + + $headers['Mime-Version'] = '1.0'; + } + //$headers['Content-Length'] = strlen($content); + $headers['Date'] = date('r', $headers['Date']); + $out = ""; + foreach ($headers as $id => $val) { + $val = str_replace("\n", "\n\t", $val); + $out .= "$id: $val\r\n"; + } + $out .= "\r\n"; + $out .= $content; + return $out; +} + +function nntp_publish($msg, $id) { + $s = fsockopen("tcp://localhost:1119"); + fgets($s); + fputs($s, "MODE STREAM\r\n"); + fgets($s); + fputs($s, "TAKETHIS $id\r\n"); + fputs($s, $msg); + fputs($s, "\r\n.\r\n"); + fgets($s); + fclose($s); +} + +function post2nntp($post, $msgid) { + global $config; + + $headers = array(); + $files = array(); + + $headers['Message-Id'] = $msgid; + $headers['Newsgroups'] = $config['nntpchan']['group']; + $headers['Date'] = time(); + $headers['Subject'] = $post['subject'] ? $post['subject'] : "None"; + $headers['From'] = $post['name'] . " "; + + if ($post['email'] == 'sage') { + $headers['X-Sage'] = true; + } + + if (!$post['op']) { + // Get muh parent + $query = prepare("SELECT `message_id` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id"); + $query->bindValue(':board', $post['board']); + $query->bindValue(':id', $post['thread']); + $query->execute() or error(db_error($query)); + + if ($result = $query->fetch(PDO::FETCH_ASSOC)) { + $headers['References'] = $result['message_id']; + } + else { + return false; // We don't have OP. Discarding. + } + } + + // Let's parse the body a bit. + $body = trim($post['body_nomarkup']); + $body = preg_replace('/\r?\n/', "\r\n", $body); + $body = preg_replace_callback('@>>(>/([a-zA-Z0-9_+-]+)/)?([0-9]+)@', function($o) use ($post) { + if ($o[1]) { + $board = $o[2]; + } + else { + $board = $post['board']; + } + $id = $o[3]; + + $query = prepare("SELECT `message_id_digest` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id"); + $query->bindValue(':board', $board); + $query->bindValue(':id', $id); + $query->execute() or error(db_error($query)); + + if ($result = $query->fetch(PDO::FETCH_ASSOC)) { + return ">>".substr($result['message_id_digest'], 0, 16); + } + else { + return $o[0]; // Should send URL imo + } + }, $body); + $body = preg_replace('/>>>>([0-9a-fA-F])+/', '>>\1', $body); + + + $files[] = array('type' => 'text/plain', 'text' => $body); + + foreach ($post['files'] as $id => $file) { + $fc = array(); + + $fc['type'] = $file['type']; + $fc['text'] = file_get_contents($file['file_path']); + $fc['name'] = $file['name']; + + $files[] = $fc; + } + + return array($headers, $files); +} diff --git a/inc/nntpchan/tests.php b/inc/nntpchan/tests.php new file mode 100644 index 00000000..a63789d7 --- /dev/null +++ b/inc/nntpchan/tests.php @@ -0,0 +1,30 @@ + "czaks ", "Message-Id" => "<1234.0000.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None"], +[['type' => 'text/plain', 'text' => "THIS IS A NEW TEST THREAD"]]); +echo "\n@@@@ Single msg:\n"; +echo $m1 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1234.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, with no image :("]]); +echo "\n@@@@ Single msg and pseudoimage:\n"; +echo $m2 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.2137.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, now with an image!"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"]]); +echo "\n@@@@ Single msg and two pseudoimages:\n"; +echo $m3 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1488.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, now WITH TWO IMAGES!!!"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif2.gif"]]); +shoveitup($m0, "<1234.0000.".$time."@example.vichan.net>"); +sleep(1); +shoveitup($m1, "<1234.1234.".$time."@example.vichan.net>"); +sleep(1); +shoveitup($m2, "<1234.2137.".$time."@example.vichan.net>"); +shoveitup($m3, "<1234.1488.".$time."@example.vichan.net>"); + diff --git a/post.php b/post.php index 4590a534..1e4edcee 100644 --- a/post.php +++ b/post.php @@ -13,7 +13,12 @@ if ((!isset($_POST['mod']) || !$_POST['mod']) && $config['board_locked']) { $dropped_post = false; +// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post. if (isset($_GET['Newsgroups']) && $config['nntpchan']['enabled']) { + if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) { + error("NNTPChan: Forbidden. $_SERVER[REMOTE_ADDR] is not a trusted peer"); + } + $_POST = array(); $_POST['json_response'] = true; @@ -1057,7 +1062,31 @@ if (isset($_POST['delete'])) { $query->bindValue(':message_id_digest', sha1($dropped_post['msgid'])); $query->bindValue(':headers', $dropped_post['headers']); $query->execute() or error(db_error($query)); + } // ^^^^^ For inbound posts ^^^^^ + elseif ($config['nntpchan']['enabled'] && $config['nntpchan']['group']) { + // vvvvv For outbound posts vvvvv + + require_once('inc/nntpchan/nntpchan.php'); + $msgid = gen_msgid($post['board'], $post['id']); + + list($headers, $files) = post2nntp($post, $msgid); + + $message = gen_nntp($headers, $files); + + $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES ". + "(:board , :id , :message_id , :message_id_digest , true , :headers)"); + + $query->bindValue(':board', $post['board']); + $query->bindValue(':id', $post['id']); + $query->bindValue(':message_id', $msgid); + $query->bindValue(':message_id_digest', sha1($msgid)); + $query->bindValue(':headers', json_encode($headers)); + $query->execute() or error(db_error($query)); + + // Let's broadcast it! + nntp_publish($message, $msgid); } + insertFloodPost($post); // Handle cyclical threads -- cgit v1.3 From 0b19051891e3a45a913cef38f0921cf781a0817b Mon Sep 17 00:00:00 2001 From: czaks Date: Mon, 15 Aug 2016 04:13:26 +0200 Subject: fix a notice; increase waiting time for dns --- inc/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 043d4de7..1b336e12 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -892,7 +892,7 @@ function displayBan($ban) { Element('page.html', array( 'title' => _('Banned!'), 'config' => $config, - 'boardlist' => createBoardlist($mod), + 'boardlist' => createBoardlist(isset($mod) ? $mod : false), 'body' => Element('banned.html', array( 'config' => $config, 'ban' => $ban, @@ -2514,7 +2514,7 @@ function rDNS($ip_addr) { if (!$config['dns_system']) { $host = gethostbyaddr($ip_addr); } else { - $resp = shell_exec_error('host -W 1 ' . $ip_addr); + $resp = shell_exec_error('host -W 3 ' . $ip_addr); if (preg_match('/domain name pointer ([^\s]+)$/', $resp, $m)) $host = $m[1]; else -- cgit v1.3 From 1c3e6e590aca050802f271d157f0d274c8217d4d Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Fri, 19 Aug 2016 16:05:50 -0400 Subject: patch for nntpchan stream --- inc/nntpchan/nntpchan.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inc/nntpchan/nntpchan.php b/inc/nntpchan/nntpchan.php index 25215e94..f21fdd2d 100644 --- a/inc/nntpchan/nntpchan.php +++ b/inc/nntpchan/nntpchan.php @@ -64,7 +64,9 @@ function gen_nntp($headers, $files) { } function nntp_publish($msg, $id) { - $s = fsockopen("tcp://localhost:1119"); + global $config; + $server = $config["nntpchan"]["server"]; + $s = fsockopen("tcp://$server"); fgets($s); fputs($s, "MODE STREAM\r\n"); fgets($s); @@ -72,6 +74,7 @@ function nntp_publish($msg, $id) { fputs($s, $msg); fputs($s, "\r\n.\r\n"); fgets($s); + fputs($s, "QUIT\r\n"); fclose($s); } -- cgit v1.3 From a5e7b3da6f8ee764ab831d3c1b2df6c17a5e78ca Mon Sep 17 00:00:00 2001 From: czaks Date: Fri, 19 Aug 2016 23:15:08 +0200 Subject: nntpchan: work around php nonsense --- inc/nntpchan/nntpchan.php | 2 +- post.php | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/inc/nntpchan/nntpchan.php b/inc/nntpchan/nntpchan.php index 25215e94..8a2fba44 100644 --- a/inc/nntpchan/nntpchan.php +++ b/inc/nntpchan/nntpchan.php @@ -124,7 +124,7 @@ function post2nntp($post, $msgid) { $query->execute() or error(db_error($query)); if ($result = $query->fetch(PDO::FETCH_ASSOC)) { - return ">>".substr($result['message_id_digest'], 0, 16); + return ">>".substr($result['message_id_digest'], 0, 18); } else { return $o[0]; // Should send URL imo diff --git a/post.php b/post.php index 1e4edcee..47c5f0ef 100644 --- a/post.php +++ b/post.php @@ -90,31 +90,36 @@ if (isset($_GET['Newsgroups']) && $config['nntpchan']['enabled']) { $content = file_get_contents("php://input"); } elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') { - _syslog(LOG_INFO, "MM: Files: ".print_r($GLOBALS, true)); + _syslog(LOG_INFO, "MM: Files: ".print_r($GLOBALS, true)); // Debug + $content = ''; - $tmpfiles = $_FILES['attachment']; - foreach ($tmpfiles as $id => $file) { - if ($file['type'] == 'text/plain') { - $content .= file_get_contents($file['tmp_name']); - unset($_FILES['attachment'][$id]); + $newfiles = array(); + foreach ($_FILES['attachment']['error'] as $id => $error) { + if ($_FILES['attachment']['type'][$id] == 'text/plain') { + $content .= file_get_contents($_FILES['attachment']['tmp_name'][$id]); } - elseif ($file['type'] == 'message/rfc822') { // Signed message, ignore for now - unset($_FILES['attachment'][$id]); + elseif ($_FILES['attachment']['type'][$id] == 'message/rfc822') { // Signed message, ignore for now } else { // A real attachment :^) + $file = array(); + $file['name'] = $_FILES['attachment']['name'][$id]; + $file['type'] = $_FILES['attachment']['type'][$id]; + $file['size'] = $_FILES['attachment']['size'][$id]; + $file['tmp_name'] = $_FILES['attachment']['tmp_name'][$id]; + $file['error'] = $_FILES['attachment']['error'][$id]; + + $newfiles["file$id"] = $file; } } - $_FILES = $_FILES['attachment']; - - + $_FILES = $newfiles; } else { error("NNTPChan: Wrong mime type: $ct"); } - $_POST['subject'] = isset($_GET['Subject']) ? $_GET['Subject'] : ''; + $_POST['subject'] = isset($_GET['Subject']) ? ($_GET['Subject'] == 'None' ? '' : $_GET['Subject']) : ''; $_POST['board'] = $xboard; if (isset ($_GET['From'])) { -- cgit v1.3 From 8951cb74c8fbface07e38308994bc2fe480e3c82 Mon Sep 17 00:00:00 2001 From: "Michael D. Reiley" Date: Thu, 22 Sep 2016 23:03:11 -0700 Subject: Rebuild index when mod deletes a thread. The index does not properly rebuild when a mod deletes a thread, resulting in a ghost thread remaining in the index until the next rebuild. This fix was originally contributed to Uboachan's codebase by Mannosuke. --- inc/mod/pages.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 303fa3dd..c3e98af5 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1721,6 +1721,8 @@ function mod_deletebyip($boardName, $post, $global = false) { deletePost($post['id'], false, false); rebuildThemes('post-delete', $board['uri']); + + buildIndex(); if ($post['thread']) $threads_to_rebuild[$post['board']][$post['thread']] = true; -- cgit v1.3 From 0feab91f31e464d11cacef6b138cc85a4d1fd924 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:26:12 +0900 Subject: Fix Content-Type header typographical error --- b.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b.php b/b.php index 11d6ad34..a0638ec3 100755 --- a/b.php +++ b/b.php @@ -23,7 +23,7 @@ function serveRandomBanner($dir, $files) { header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1 header('Pragma: no-cache'); // HTTP 1.0 header('Expires: 0'); // Proxies - header("Content-type: image/" . ext); + header("Content-type: image/" . $ext); header("Content-Disposition: inline; filename=" . $name); // readfile displays the image, passthru seems to spits stream. -- cgit v1.3 From 05569d150abac93b306d923679af6122c89e3b62 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:29:35 +0900 Subject: Updated copyright year --- templates/thread.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/templates/thread.html b/templates/thread.html index 62fe2c12..346db449 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -40,8 +40,10 @@ {% if config.global_message %}
    {{ config.global_message }}
    {% endif %}
    - [{% trans %}Return{% endtrans %}] - [{% trans %}Go to bottom{% endtrans %}] +
    @@ -60,8 +62,8 @@ vichan + lainchan {{ config.version }} -
    Tinyboard Copyright © 2010-2014 Tinyboard Development Group -
    vichan Copyright © 2012-2015 vichan-devel -
    lainchan Copyright © 2014-2015 lainchan Administration

    +
    vichan Copyright © 2012-2016 vichan-devel +
    lainchan Copyright © 2014-2016 lainchan Administration

    {% for footer in config.footer %}

    {{ footer }}

    {% endfor %}
    -- cgit v1.3 From 8b5688e12a3687f86c8c40d36a1327445cfb8143 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:33:20 +0900 Subject: Add porn to public banlist theme --- templates/themes/public_banlist/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/themes/public_banlist/theme.php b/templates/themes/public_banlist/theme.php index c9f7e6f0..62a794dd 100644 --- a/templates/themes/public_banlist/theme.php +++ b/templates/themes/public_banlist/theme.php @@ -25,7 +25,7 @@ public static function gen_json($settings) { ob_start(); - Bans::stream_json(false, true, true, array(), array("/\bcp\b/i")); + Bans::stream_json(false, true, true, array(), array("/\bcp\b/i", "/porn/i")); $out = ob_get_contents(); ob_end_clean(); return $out; -- cgit v1.3 From 31b69902bbfdccffd0ed7ba32f04aaf1479d41b7 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:38:24 +0900 Subject: Add catalog support to all of rand, semi,rand and ukko themes --- templates/index.html | 9 +++++-- templates/themes/catalog/info.php | 7 ++++++ templates/themes/catalog/theme.php | 47 +++++++++++++++++++++++++++++++++++++ templates/themes/rand/theme.php | 1 + templates/themes/semirand/theme.php | 1 + templates/themes/ukko/theme.php | 1 + 6 files changed, 64 insertions(+), 2 deletions(-) diff --git a/templates/index.html b/templates/index.html index 1878d455..2241b13c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -58,6 +58,11 @@ {% if config.global_message %}
    {{ config.global_message }}
    {% endif %}
    + {% if config.catalog_link %} + | Catalog + {% endif %} +
    + {% if mod %}{% endif %} @@ -72,8 +77,8 @@ vichan + lainchan {{ config.version }} -
    Tinyboard Copyright © 2010-2014 Tinyboard Development Group -
    vichan Copyright © 2012-2015 vichan-devel -
    lainchan Copyright © 2014-2015 lainchan Administration

    +
    vichan Copyright © 2012-2016 vichan-devel +
    lainchan Copyright © 2014-2016 lainchan Administration

    {% for footer in config.footer %}

    {{ footer }}

    {% endfor %} diff --git a/templates/themes/catalog/info.php b/templates/themes/catalog/info.php index e2b13307..66c45082 100644 --- a/templates/themes/catalog/info.php +++ b/templates/themes/catalog/info.php @@ -46,6 +46,13 @@ 'comment' => 'Enable catalog for the Ukko theme. This requires the Ukko theme to be enabled.' ); + $theme['config'][] = Array( + 'title' => 'Enable Rand catalog', + 'name' => 'enable_rand', + 'type' => 'checkbox', + 'default' => false, + 'comment' => 'Enable catalog for the Rand theme. This requires the Rand theme to be enabled.' + ); $theme['config'][] = Array( 'title' => 'Use tooltipster', 'name' => 'use_tooltipster', diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index 9ecdb9d1..1199b54a 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -40,6 +40,14 @@ { $b->buildUkko(); } + + // FIXME: Check that Rand is actually enabled + if ($settings['enable_rand'] && ( + $action === 'all' || $action === 'post' || + $action === 'post-thread' || $action === 'post-delete')) + { + $b->buildRand(); + } } // Wrap functions in a class so they don't interfere with normal Tinyboard operations @@ -90,6 +98,45 @@ $this->saveForBoard($ukkoSettings['uri'], $recent_posts, $config['root'] . $ukkoSettings['uri']); } + + /** + * Build and save the HTML of the catalog for the Rand theme + */ + public function buildRand() { + global $config; + + $randSettings = themeSettings('rand'); + $queries = array(); + $threads = array(); + + $exclusions = explode(' ', $randSettings['exclude']); + $boards = array_diff(listBoards(true), $exclusions); + + foreach ($boards as $b) { + if (array_key_exists($b, $this->threadsCache)) { + $threads = array_merge($threads, $this->threadsCache[$b]); + } else { + $queries[] = $this->buildThreadsQuery($b); + } + } + + // Fetch threads from boards that haven't beenp processed yet + if (!empty($queries)) { + $sql = implode(' UNION ALL ', $queries); + $res = query($sql) or error(db_error()); + $threads = array_merge($threads, $res->fetchAll(PDO::FETCH_ASSOC)); + } + + // Sort in bump order + usort($threads, function($a, $b) { + return strcmp($b['bump'], $a['bump']); + }); + // Generate data for the template + $recent_posts = $this->generateRecentPosts($threads); + + $this->saveForBoard($randSettings['uri'], $recent_posts, + $config['root'] . $randSettings['uri']); + } /** * Build and save the HTML of the catalog for the given board diff --git a/templates/themes/rand/theme.php b/templates/themes/rand/theme.php index b4cfb4ff..e3814c4e 100644 --- a/templates/themes/rand/theme.php +++ b/templates/themes/rand/theme.php @@ -18,6 +18,7 @@ $body = ''; $overflow = array(); $board = array( + 'dir' => $this->settings['uri'] . "/", 'url' => $this->settings['uri'], 'name' => $this->settings['title'], 'title' => sprintf($this->settings['subtitle'], $this->settings['thread_limit']) diff --git a/templates/themes/semirand/theme.php b/templates/themes/semirand/theme.php index 1acecbfb..e8d3e56c 100644 --- a/templates/themes/semirand/theme.php +++ b/templates/themes/semirand/theme.php @@ -225,6 +225,7 @@ return Element('index.html', array( 'config' => $config, 'board' => array( + 'dir' => $this->settings['uri'] . "/", 'url' => $this->settings['uri'], 'title' => $this->settings['title'], 'subtitle' => str_replace('%s', $this->settings['thread_limit'], diff --git a/templates/themes/ukko/theme.php b/templates/themes/ukko/theme.php index 05525299..92dfb317 100644 --- a/templates/themes/ukko/theme.php +++ b/templates/themes/ukko/theme.php @@ -28,6 +28,7 @@ $body = ''; $overflow = array(); $board = array( + 'dir' => $this->settings['uri'] . "/", 'url' => $this->settings['uri'], 'uri' => $this->settings['uri'], 'name' => $this->settings['title'], -- cgit v1.3 From ac4ddfec8ab8fb2d63524deb28be0de222e7ea0f Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:39:21 +0900 Subject: Update donate page --- templates/themes/donate/donate.html | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/templates/themes/donate/donate.html b/templates/themes/donate/donate.html index 73c06cb4..1ffe0976 100644 --- a/templates/themes/donate/donate.html +++ b/templates/themes/donate/donate.html @@ -16,32 +16,18 @@

    {{ settings.title }}

    {{ settings.subtitle }}
    - +
    mascot - -
    +
    +

    Thanks for donating

    -

    The server costs $55 per month. If you buy stickers we get about 45-55% of whatever you spend. I have to pay my phone bill to keep two-factor authentication working thats $45 (I pay this myself if im not homeless for the month). I just purchased a few lainchan related domains with whatever BTC we had. $50 or so of donations needs to go to darkengine soon to reimburse him for stickers. I have to pay the bill for my Macbook too sometimes. I want to spend a bunch of donation money on a bulk order of stickers and give em away as prizes or just spam shit in the city. FOOFDOOF is the top donator, hes given us like almost $1000. Sometimes I spend the donation money on food when i'm starving. I transfer all donations into, and keep them as cash. - +

    The server costs $55 per month. If you buy stickers we get about 45-55% of whatever you spend. ForeverN2Dust is the top donator, giving about $3000. FOOFDOOF is #2 with $1000 and lots of cheap Chinese shit. Please specify what you'd like your money to go towards. We spent $100 for advertising in March.

    -

    The most secure and private way to donate is probably to send cash in the mail to me, but this option is only available to select people until I set up a PO box or something.

    https://www.stickermule.com/marketplace/tags/lainchan

    -

    Localbitcoins.com is a good place to buy BTC. You can donate BTC to this address: 18CshTkxW6HRFoBhXo9mLJcjkReMmjvuq9

    - buttcoin qr - -

    The absolute fastest way for me to receive and process the money is with a Credit/Debit/Prepaid card via paypal here:

    - - - - - - - - -
    + {% endfilter %} -- cgit v1.3 From e7662d385e9e852f98177435769aa9d9b08d8c4d Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:40:22 +0900 Subject: Fix irc theme to actually be IRC --- templates/themes/irc/irc.html | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/templates/themes/irc/irc.html b/templates/themes/irc/irc.html index 9fadff99..2828b6e0 100644 --- a/templates/themes/irc/irc.html +++ b/templates/themes/irc/irc.html @@ -1,48 +1,29 @@ {% filter remove_whitespace %} - + {{ settings.title }} - + + - - -
    - {{ boardlist.top }} + + +
    + {{ boardlist.top }}
    - {% if config.url_banner %}{% endif %}

    {{ settings.title }}

    {{ settings.subtitle }}
    - mascot -
    -

    WELCOME TO CYBERIA

    - -

    Welcome to lainchan's #BADA55 radio stream.
    Statistics - about this stream such as current listeners, song title, et - cetera will soon be added to this page. -

    - - - - +
    +

    Web Client - Point your own clients to #lainchan on irc.freenode.net.

    +
    -- cgit v1.3 From e7506c1961664c7eb2390803f4f5bd89e5b730d3 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:40:55 +0900 Subject: Fix radio them to actually be Radio --- templates/themes/radio/radio.html | 66 +++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/templates/themes/radio/radio.html b/templates/themes/radio/radio.html index 2828b6e0..b19d703a 100644 --- a/templates/themes/radio/radio.html +++ b/templates/themes/radio/radio.html @@ -1,29 +1,75 @@ {% filter remove_whitespace %} - + {{ settings.title }} - - + - - -
    - {{ boardlist.top }} + + + + +
    + {{ boardlist.top }}
    + {% if config.url_banner %}{% endif %}

    {{ settings.title }}

    {{ settings.subtitle }}
    + mascot + +
    +

    WELCOME TO CYBERIA

    + +

    Welcome to lainchan's radio stream. +

    + + +

    Now playing: +

    +

    Current listeners: +

    + + -
    -

    Web Client - Point your own clients to #lainchan on irc.freenode.net.

    - +
    -- cgit v1.3 From df4c402f1add7547d472f94934c5137faf23aef4 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:43:05 +0900 Subject: Fix header margin --- stylesheets/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stylesheets/style.css b/stylesheets/style.css index 0d713b79..1fa2d4a8 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -244,7 +244,7 @@ h2 { } header { - margin: 1em 0; + margin: 2em 0; } h1 { -- cgit v1.3 From d09953da0750347f965dcdebd7a769fed98945ee Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:44:48 +0900 Subject: ReportNotifier Slack notification support --- inc/config.php | 6 ++++++ post.php | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/inc/config.php b/inc/config.php index 69b4bd3a..86ddd5aa 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1669,3 +1669,9 @@ ''. ''. '
    '; + + // Slack Report Notification + $config['slack'] = true; + $config['slack_channel'] = "reports"; + $config['slack_incoming_webhook_endpoint'] = "https://hooks.slack.com/services/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"; + diff --git a/post.php b/post.php index 99ef4850..f2ed509f 100644 --- a/post.php +++ b/post.php @@ -141,15 +141,15 @@ if (isset($_POST['delete'])) { markup($reason); foreach ($report as &$id) { - $query = prepare(sprintf("SELECT `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); + $query = prepare(sprintf("SELECT `thread` , `body_nomarkup` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); - $thread = $query->fetchColumn(); + $thread = $query->fetch(PDO::FETCH_ASSOC); if ($config['syslog']) _syslog(LOG_INFO, 'Reported post: ' . - '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($thread ? '#' . $id : '') . + '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($thread['thread'] ? '#' . $id : '') . ' for "' . $reason . '"' ); $query = prepare("INSERT INTO ``reports`` VALUES (NULL, :time, :ip, :board, :post, :reason)"); @@ -159,6 +159,38 @@ if (isset($_POST['delete'])) { $query->bindValue(':post', $id, PDO::PARAM_INT); $query->bindValue(':reason', $reason, PDO::PARAM_STR); $query->execute() or error(db_error($query)); + if ($config['slack']) + { + function slack($message, $room = "reports", $icon = ":no_entry_sign:") + { + $room = ($room) ? $room : "reports"; + $data = "payload=" . json_encode(array( + "channel" => "#{$room}", + "text" => urlencode($message), + "icon_emoji" => $icon + )); + + // You can get your webhook endpoint from your Slack settings + // For some reason using the configuration key doesn't work + //$ch = curl_init($config['slack_incoming_webhook_endpoint']); + $ch = curl_init("https://hooks.slack.com/services/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"); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + curl_close($ch); + + return $result; + } + + $postcontent = mb_substr($thread['body_nomarkup'], 0, 120) . '... _*(POST TRIMMED)*_'; + $slackmessage = 'Post: <' .$config['domain'] . "/mod.php?/" . $board['dir'] . $config['dir']['res'] . ( $thread['thread'] ? $thread['thread'] : $id ) . ".html" . ($thread['thread'] ? '#' . $id : '') . '> \n Post Content: ' . $postcontent . '\n Reason: ' . $reason . '\n'; + + $slackresult = slack($slackmessage, $config['slack_channel']); + } + + } $is_mod = isset($_POST['mod']) && $_POST['mod']; -- cgit v1.3 From 5d08f0f04807cb0b88177d74da25d1242765868f Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 22:45:56 +0900 Subject: Change font-size pt to px for heavy ice --- stylesheets/heavy_ice.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stylesheets/heavy_ice.css b/stylesheets/heavy_ice.css index 572f303e..cbdc9994 100644 --- a/stylesheets/heavy_ice.css +++ b/stylesheets/heavy_ice.css @@ -62,7 +62,7 @@ h1 font-family: 'lain', tahoma; letter-spacing: -2px; - font-size: 42pt; + font-size: 42px; text-align: center; color: #d2e7e8; @@ -293,7 +293,7 @@ div.pages background: #333; border: #666 0px solid; font-family: sans-serif; - font-size: 10pt; + font-size: 10px; } div.pages a.selected { @@ -326,7 +326,7 @@ div.ban h2 background: #333; color: #d2e7e8; padding: 3px 7px; - font-size: 12pt; + font-size: 12px; border-bottom: 1px solid #555; } div.ban h2:not(:nth-child(1)) -- cgit v1.3 From 59cc269cd70147a738318cc50da0b9d4691b6ce3 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Fri, 23 Sep 2016 23:05:04 +0900 Subject: Change ReportNotifier SlackMessage format --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index f2ed509f..045bec45 100644 --- a/post.php +++ b/post.php @@ -185,7 +185,7 @@ if (isset($_POST['delete'])) { } $postcontent = mb_substr($thread['body_nomarkup'], 0, 120) . '... _*(POST TRIMMED)*_'; - $slackmessage = 'Post: <' .$config['domain'] . "/mod.php?/" . $board['dir'] . $config['dir']['res'] . ( $thread['thread'] ? $thread['thread'] : $id ) . ".html" . ($thread['thread'] ? '#' . $id : '') . '> \n Post Content: ' . $postcontent . '\n Reason: ' . $reason . '\n'; + $slackmessage = ' <' .$config['domain'] . "/mod.php?/" . $board['dir'] . $config['dir']['res'] . ( $thread['thread'] ? $thread['thread'] : $id ) . ".html" . ($thread['thread'] ? '#' . $id : '') . '> \n ' . $reason. '\n ' . $postcontent . '\n'; $slackresult = slack($slackmessage, $config['slack_channel']); } -- cgit v1.3 From 5af60d1e3324cf4c5d12a39b130de6343ed53382 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 24 Sep 2016 12:52:08 +0900 Subject: Remove leading space from ReportNotifier Slack Message --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index 045bec45..718b2833 100644 --- a/post.php +++ b/post.php @@ -185,7 +185,7 @@ if (isset($_POST['delete'])) { } $postcontent = mb_substr($thread['body_nomarkup'], 0, 120) . '... _*(POST TRIMMED)*_'; - $slackmessage = ' <' .$config['domain'] . "/mod.php?/" . $board['dir'] . $config['dir']['res'] . ( $thread['thread'] ? $thread['thread'] : $id ) . ".html" . ($thread['thread'] ? '#' . $id : '') . '> \n ' . $reason. '\n ' . $postcontent . '\n'; + $slackmessage = '<' .$config['domain'] . "/mod.php?/" . $board['dir'] . $config['dir']['res'] . ( $thread['thread'] ? $thread['thread'] : $id ) . ".html" . ($thread['thread'] ? '#' . $id : '') . '> \n ' . $reason . '\n ' . $postcontent . '\n'; $slackresult = slack($slackmessage, $config['slack_channel']); } -- cgit v1.3 From 9436524a306c8e3d6d8a6ed195e2a3c56d5cde39 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 24 Sep 2016 20:19:08 +0900 Subject: Added donation progress bar to donate theme --- templates/themes/donate/donate.html | 179 +++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 2 deletions(-) diff --git a/templates/themes/donate/donate.html b/templates/themes/donate/donate.html index 1ffe0976..551eea62 100644 --- a/templates/themes/donate/donate.html +++ b/templates/themes/donate/donate.html @@ -8,6 +8,166 @@ {% if config.font_awesome %}{% endif %} +
    {{ boardlist.top }}
    @@ -21,10 +181,25 @@

    Thanks for donating

    -

    The server costs $55 per month. If you buy stickers we get about 45-55% of whatever you spend. ForeverN2Dust is the top donator, giving about $3000. FOOFDOOF is #2 with $1000 and lots of cheap Chinese shit. Please specify what you'd like your money to go towards. We spent $100 for advertising in March. +

    The server costs $55 per month. If you buy stickers we get about 45-55% of whatever you spend. Historical donation records to the former owner are ForeverN2Dust is the top donator, giving about $3000. FOOFDOOF is #2 with $1000 and lots of cheap Chinese shit. Please specify what you'd like your money to go towards. If you don't want to buy stickers, but want to donate something else please either ask in /q/ or contact the Lainchan administration on IRC at #lainchan on Freenode.

    -

    https://www.stickermule.com/marketplace/tags/lainchan

    +

    Buy stickers here +

    + +

    Monthly Funding Progress Bar

    + +

    Cost per month $55

    + +

    Funds from stickers $37.75

    + +
    +
    + +
    +
    $-19.25
    +
    -- cgit v1.3 From ea9da7da611509fbe9c5f23867414242a443e3a8 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sun, 2 Oct 2016 01:15:34 +0900 Subject: Make slack report notifier, donate page, irc page and radio page use configuration variables instead of hard coding values. --- inc/config.php | 6 +- post.php | 52 +++++------ stylesheets/progressbar.css | 159 +++++++++++++++++++++++++++++++++ templates/themes/donate/donate.html | 173 ++---------------------------------- templates/themes/donate/info.php | 33 ++++++- templates/themes/irc/info.php | 7 +- templates/themes/irc/irc.html | 4 +- templates/themes/irc/radio.html | 29 ------ templates/themes/radio/info.php | 9 +- templates/themes/radio/radio.html | 2 +- 10 files changed, 244 insertions(+), 230 deletions(-) create mode 100644 stylesheets/progressbar.css delete mode 100644 templates/themes/irc/radio.html diff --git a/inc/config.php b/inc/config.php index dc65068f..0f2d40fc 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1811,9 +1811,9 @@ '
    '; // Slack Report Notification - $config['slack'] = true; - $config['slack_channel'] = "reports"; - $config['slack_incoming_webhook_endpoint'] = "https://hooks.slack.com/services/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"; + $config['slack'] = false; + $config['slack_channel'] = ""; + $config['slack_incoming_webhook_endpoint'] = "https://hooks.slack.com/services/"; // Password hashing function // diff --git a/post.php b/post.php index 070cbd88..84858bb2 100644 --- a/post.php +++ b/post.php @@ -342,36 +342,38 @@ if (isset($_POST['delete'])) { $query->bindValue(':post', $id, PDO::PARAM_INT); $query->bindValue(':reason', $reason, PDO::PARAM_STR); $query->execute() or error(db_error($query)); - if ($config['slack']) - { - function slack($message, $room = "reports", $icon = ":no_entry_sign:") - { - $room = ($room) ? $room : "reports"; - $data = "payload=" . json_encode(array( - "channel" => "#{$room}", - "text" => urlencode($message), - "icon_emoji" => $icon - )); - - // You can get your webhook endpoint from your Slack settings - // For some reason using the configuration key doesn't work - //$ch = curl_init($config['slack_incoming_webhook_endpoint']); - $ch = curl_init("https://hooks.slack.com/services/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"); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $result = curl_exec($ch); - curl_close($ch); - - return $result; - } + + if ($config['slack']) + { + + function slack($message, $room = "reports", $icon = ":no_entry_sign:") + { + $room = ($room) ? $room : "reports"; + $data = "payload=" . json_encode(array( + "channel" => "#{$room}", + "text" => urlencode($message), + "icon_emoji" => $icon + )); + + // You can get your webhook endpoint from your Slack settings + // For some reason using the configuration key doesn't work + $ch = curl_init($config['slack_incoming_webhook_endpoint']); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + curl_close($ch); + + return $result; + } $postcontent = mb_substr($thread['body_nomarkup'], 0, 120) . '... _*(POST TRIMMED)*_'; $slackmessage = '<' .$config['domain'] . "/mod.php?/" . $board['dir'] . $config['dir']['res'] . ( $thread['thread'] ? $thread['thread'] : $id ) . ".html" . ($thread['thread'] ? '#' . $id : '') . '> \n ' . $reason . '\n ' . $postcontent . '\n'; $slackresult = slack($slackmessage, $config['slack_channel']); - } + + } } diff --git a/stylesheets/progressbar.css b/stylesheets/progressbar.css new file mode 100644 index 00000000..6083e72e --- /dev/null +++ b/stylesheets/progressbar.css @@ -0,0 +1,159 @@ + /*---------------------------*/ + .container { + margin: auto; + width : 50%; + } + .progress-bar { + background-color: #1a1a1a; + height: 25px; + padding: 5px; + width: 350px; + position: relative; + margin: 70px 0 20px 0; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + -moz-box-shadow: 0 1px 5px #000 inset, 0 1px 0 #444; + -webkit-box-shadow: 0 1px 5px #000 inset, 0 1px 0 #444; + box-shadow: 0 1px 5px #000 inset, 0 1px 0 #444; + } + + .progress-bar span { + display: inline-block; + height: 100%; + background-color: #777; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset; + -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset; + box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset; + -webkit-transition: width .4s ease-in-out; + -moz-transition: width .4s ease-in-out; + -ms-transition: width .4s ease-in-out; + -o-transition: width .4s ease-in-out; + transition: width .4s ease-in-out; + } + + + .blue span { + background-color: #34c2e3; + } + + .red span { + background-color: #f42323; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f42323), to(#ff0000)); + background-image: -webkit-linear-gradient(top, #f42323, #ff0000); + background-image: -moz-linear-gradient(top, #f42323, #ff0000); + background-image: -ms-linear-gradient(top, #f42323, #ff0000); + background-image: -o-linear-gradient(top, #f42323, #ff0000); + background-image: linear-gradient(top, #f42323, #ff0000); + } + + .green span { + background-color: #a5df41; + background-image: -webkit-gradient(linear, left top, left bottom, from(#a5df41), to(#4ca916)); + background-image: -webkit-linear-gradient(top, #a5df41, #4ca916); + background-image: -moz-linear-gradient(top, #a5df41, #4ca916); + background-image: -ms-linear-gradient(top, #a5df41, #4ca916); + background-image: -o-linear-gradient(top, #a5df41, #4ca916); + background-image: linear-gradient(top, #a5df41, #4ca916); + } + + /*---------------------------*/ + + .stripes span { + -webkit-background-size: 30px 30px; + -moz-background-size: 30px 30px; + background-size: 30px 30px; + background-image: -webkit-gradient(linear, left top, right bottom, + color-stop(.25, rgba(255, 255, 255, .15)), color-stop(.25, transparent), + color-stop(.5, transparent), color-stop(.5, rgba(255, 255, 255, .15)), + color-stop(.75, rgba(255, 255, 255, .15)), color-stop(.75, transparent), + to(transparent)); + background-image: -webkit-linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%, + transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, + transparent 75%, transparent); + background-image: -moz-linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%, + transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, + transparent 75%, transparent); + background-image: -ms-linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%, + transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, + transparent 75%, transparent); + background-image: -o-linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%, + transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, + transparent 75%, transparent); + background-image: linear-gradient(135deg, rgba(255, 255, 255, .15) 25%, transparent 25%, + transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, + transparent 75%, transparent); + + -webkit-animation: animate-stripes 3s linear infinite; + -moz-animation: animate-stripes 3s linear infinite; + } + + @-webkit-keyframes animate-stripes { + 0% {background-position: 0 0;} 100% {background-position: 60px 0;} + } + + + @-moz-keyframes animate-stripes { + 0% {background-position: 0 0;} 100% {background-position: 60px 0;} + } + + /*---------------------------*/ + + .shine span { + position: relative; + } + + .shine span::after { + content: ''; + opacity: 0; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: #fff; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + + -webkit-animation: animate-shine 2s ease-out infinite; + -moz-animation: animate-shine 2s ease-out infinite; + } + + @-webkit-keyframes animate-shine { + 0% {opacity: 0; width: 0;} + 50% {opacity: .5;} + 100% {opacity: 0; width: 95%;} + } + + + @-moz-keyframes animate-shine { + 0% {opacity: 0; width: 0;} + 50% {opacity: .5;} + 100% {opacity: 0; width: 95%;} + } + .glow span { + -moz-box-shadow: 0 5px 5px rgba(255, 255, 255, .7) inset, 0 -5px 5px rgba(255, 255, 255, .7) inset; + -webkit-box-shadow: 0 5px 5px rgba(255, 255, 255, .7) inset, 0 -5px 5px rgba(255, 255, 255, .7) inset; + box-shadow: 0 5px 5px rgba(255, 255, 255, .7) inset, 0 -5px 5px rgba(255, 255, 255, .7) inset; + + -webkit-animation: animate-glow 1s ease-out infinite; + -moz-animation: animate-glow 1s ease-out infinite; + } + + @-webkit-keyframes animate-glow { + 0% { -webkit-box-shadow: 0 5px 5px rgba(255, 255, 255, .7) inset, 0 -5px 5px rgba(255, 255, 255, .7) inset;} + 50% { -webkit-box-shadow: 0 5px 5px rgba(255, 255, 255, .3) inset, 0 -5px 5px rgba(255, 255, 255, .3) inset;} + 100% { -webkit-box-shadow: 0 5px 5px rgba(255, 255, 255, .7) inset, 0 -5px 5px rgba(255, 255, 255, .7) inset;} + } + + @-moz-keyframes animate-glow { + 0% { -moz-box-shadow: 0 5px 5px rgba(255, 255, 255, .7) inset, 0 -5px 5px rgba(255, 255, 255, .7) inset;} + 50% { -moz-box-shadow: 0 5px 5px rgba(255, 255, 255, .3) inset, 0 -5px 5px rgba(255, 255, 255, .3) inset;} + 100% { -moz-box-shadow: 0 5px 5px rgba(255, 255, 255, .7) inset, 0 -5px 5px rgba(255, 255, 255, .7) inset;} + } + + diff --git a/templates/themes/donate/donate.html b/templates/themes/donate/donate.html index 551eea62..27f16747 100644 --- a/templates/themes/donate/donate.html +++ b/templates/themes/donate/donate.html @@ -7,167 +7,8 @@ + {% if config.font_awesome %}{% endif %} -
    {{ boardlist.top }}
    @@ -181,7 +22,7 @@

    Thanks for donating

    -

    The server costs $55 per month. If you buy stickers we get about 45-55% of whatever you spend. Historical donation records to the former owner are ForeverN2Dust is the top donator, giving about $3000. FOOFDOOF is #2 with $1000 and lots of cheap Chinese shit. Please specify what you'd like your money to go towards. If you don't want to buy stickers, but want to donate something else please either ask in /q/ or contact the Lainchan administration on IRC at #lainchan on Freenode. +

    The server costs {{ settings.monthlyamount }} per month. If you buy stickers we get about 45-55% of whatever you spend. Historical donation records to the former owner are ForeverN2Dust is the top donator, giving about $3000. FOOFDOOF is #2 with $1000 and lots of cheap Chinese shit. Please specify what you'd like your money to go towards. If you don't want to buy stickers, but want to donate something else please either ask in /q/ or contact the Lainchan administration on IRC at #lainchan on Freenode.

    Buy stickers here @@ -189,16 +30,16 @@

    Monthly Funding Progress Bar

    -

    Cost per month $55

    +

    Cost per month {{ settings.monthlyamount }}

    -

    Funds from stickers $37.75

    +

    Funds from stickers {{ settings.currentamount }}

    -
    - +
    -
    $-19.25
    +
    {{ settings.currentprogress }}
    diff --git a/templates/themes/donate/info.php b/templates/themes/donate/info.php index fa3f649b..2b618e89 100644 --- a/templates/themes/donate/info.php +++ b/templates/themes/donate/info.php @@ -17,7 +17,38 @@ $theme = array( array('title' => 'File', 'name' => 'file', 'type' => 'text', - 'default' => 'donate.html')), + 'default' => 'donate.html'), + + array('title' => 'Monthly Amount', + 'name' => 'monthlyamount', + 'type' => 'text', + 'default' => '$55'), + + array('title' => 'Current Amount', + 'name' => 'currentamount', + 'type' => 'text', + 'default' => '$0'), + + array('title' => 'Current Progress', + 'name' => 'currentprogress', + 'type' => 'text', + 'default' => '$0'), + + array('title' => 'Progress Bar CSS class', + 'name' => 'progressbarcssclass', + 'type' => 'text', + 'default' => 'progress-bar red glow'), + + array('title' => 'Progress Bar CSS left', + 'name' => 'progressbarcssleft', + 'type' => 'text', + 'default' => '36'), + + array('title' => 'Progress Bar Text CSS left', + 'name' => 'progressbartextcssleft', + 'type' => 'text', + 'default' => '135'), + ), 'build_function' => 'donate_build'); ?> diff --git a/templates/themes/irc/info.php b/templates/themes/irc/info.php index 8b31b011..59ff4bd6 100644 --- a/templates/themes/irc/info.php +++ b/templates/themes/irc/info.php @@ -17,7 +17,12 @@ $theme = array( array('title' => 'File', 'name' => 'file', 'type' => 'text', - 'default' => 'irc.html')), + 'default' => 'irc.html'), + + array('title' => 'Channel', + 'name' => 'channel', + 'type' => 'text', + 'default' => 'lainchan')), 'build_function' => 'irc_build'); ?> diff --git a/templates/themes/irc/irc.html b/templates/themes/irc/irc.html index 2828b6e0..1766f426 100644 --- a/templates/themes/irc/irc.html +++ b/templates/themes/irc/irc.html @@ -22,8 +22,8 @@
    -

    Web Client - Point your own clients to #lainchan on irc.freenode.net.

    - +

    Web Client - Point your own clients to #{{ settings.channel }} on irc.freenode.net.

    +
    diff --git a/templates/themes/irc/radio.html b/templates/themes/irc/radio.html deleted file mode 100644 index 7f84134b..00000000 --- a/templates/themes/irc/radio.html +++ /dev/null @@ -1,29 +0,0 @@ -{% filter remove_whitespace %} - - - - - {{ settings.title }} - - - - - - -
    - {{ boardlist.top }} -
    - -
    -

    {{ settings.title }}

    -
    {{ settings.subtitle }}
    -
    - - -
    -

    Web Client - Point your own clients to #lainchan on irc.freenode.net.

    - -
    - - -{% endfilter %} diff --git a/templates/themes/radio/info.php b/templates/themes/radio/info.php index d6679733..2ae31fbd 100644 --- a/templates/themes/radio/info.php +++ b/templates/themes/radio/info.php @@ -17,7 +17,12 @@ $theme = array( array('title' => 'File', 'name' => 'file', 'type' => 'text', - 'default' => 'radio.html')), + 'default' => 'radio.html'), + + array('title' => 'Radio Status URL', + 'name' => 'radiostatus', + 'type' => 'text', + 'default' => '/radio_assets/status.xsl')), 'build_function' => 'radio_build'); -?> \ No newline at end of file +?> diff --git a/templates/themes/radio/radio.html b/templates/themes/radio/radio.html index b19d703a..3e16430e 100644 --- a/templates/themes/radio/radio.html +++ b/templates/themes/radio/radio.html @@ -16,7 +16,7 @@ $(document).ready(function(){ }); function check_status(){ -$.get("/radio_assets/status.xsl", function(data){ +$.get("{{ settings.radiostatus }}", function(data){ var el = $( '
    ' ); el.html(data); var sd = el.find(".streamdata"); -- cgit v1.3 From c0d1f60752f9a9c2ff6c2516cd7edd946fd3a441 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 8 Oct 2016 00:47:55 +0900 Subject: Add other donation fields to donate theme / extension --- templates/themes/donate/donate.html | 4 +++- templates/themes/donate/info.php | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/templates/themes/donate/donate.html b/templates/themes/donate/donate.html index 27f16747..1ecf008e 100644 --- a/templates/themes/donate/donate.html +++ b/templates/themes/donate/donate.html @@ -32,7 +32,9 @@

    Cost per month {{ settings.monthlyamount }}

    -

    Funds from stickers {{ settings.currentamount }}

    +

    Funds from stickers {{ settings.stickeramount }}

    + +

    Funds from other donations {{ settings.otheramount }}

    diff --git a/templates/themes/donate/info.php b/templates/themes/donate/info.php index 2b618e89..6fa57cd6 100644 --- a/templates/themes/donate/info.php +++ b/templates/themes/donate/info.php @@ -24,8 +24,13 @@ $theme = array( 'type' => 'text', 'default' => '$55'), - array('title' => 'Current Amount', - 'name' => 'currentamount', + array('title' => 'Current Sticker Amount', + 'name' => 'stickeramount', + 'type' => 'text', + 'default' => '$0'), + + array('title' => 'Current Other Donations Amount', + 'name' => 'otheramount', 'type' => 'text', 'default' => '$0'), -- cgit v1.3 From 379fe63fba2bd74584a4af0a209022207974c66c Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 8 Oct 2016 00:48:55 +0900 Subject: Add enable per board feature and only include new threads feature for RSS theme / extension --- templates/themes/rss/info.php | 14 +++++ templates/themes/rss/rss.xml | 2 +- templates/themes/rss/theme.php | 114 +++++++++++++++++++++-------------------- 3 files changed, 74 insertions(+), 56 deletions(-) diff --git a/templates/themes/rss/info.php b/templates/themes/rss/info.php index 14b39eb9..4da43bf0 100755 --- a/templates/themes/rss/info.php +++ b/templates/themes/rss/info.php @@ -48,6 +48,20 @@ 'comment' => '(eg. "http://test.com")' ); + $theme['config'][] = Array( + 'title' => 'Show Threads Only', + 'name' => 'show_threads_only', + 'type' => 'text', + 'default' => 'false', + ); + + $theme['config'][] = Array( + 'title' => 'Enable Per Board', + 'name' => 'enable_per_board', + 'type' => 'text', + 'default' => 'false', + ); + // Unique function name for building everything $theme['build_function'] = 'rss_recentposts_build'; $theme['install_callback'] = 'rss_recentposts_install'; diff --git a/templates/themes/rss/rss.xml b/templates/themes/rss/rss.xml index 30fdb473..6bf34113 100644 --- a/templates/themes/rss/rss.xml +++ b/templates/themes/rss/rss.xml @@ -11,7 +11,7 @@ {{ settings.base_url }}{{ config.root }}{{ settings.html }} {% for post in recent_posts %} - {{ post.board_name }} + <![CDATA[ {{ post.board_name }} ]]> diff --git a/templates/themes/rss/theme.php b/templates/themes/rss/theme.php index 33c1b210..d0a80f51 100755 --- a/templates/themes/rss/theme.php +++ b/templates/themes/rss/theme.php @@ -16,52 +16,91 @@ // Wrap functions in a class so they don't interfere with normal Tinyboard operations class RSSRecentPosts { public function build($action, $settings) { - global $config, $_theme; + global $config, $_theme,$board; - /*if ($action == 'all') { - copy('templates/themes/recent/' . $settings['basecss'], $config['dir']['home'] . $settings['css']); - }*/ $this->excluded = explode(' ', $settings['exclude']); - - if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') - file_write($config['dir']['home'] . $settings['xml'], $this->homepage($settings)); + if ($settings['show_threads_only'] == 'true'){ + if ($action == 'all' || $action == 'post-thread' || $action == 'post-delete') + file_write($config['dir']['home'] . $settings['xml'], $this->site_rsspage($settings)); + } + else { + if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') + file_write($config['dir']['home'] . $settings['xml'], $this->site_rsspage($settings)); + } + + if ($settings['enable_per_board'] == 'true'){ + $boards = listBoards(); + foreach ($boards as &$_board) { + if (in_array($_board['uri'], $this->excluded)) + continue; + + openBoard($_board['uri']); + if ($settings['show_threads_only'] == 'true'){ + if ($action == 'all' || $action == 'post-thread' || $action == 'post-delete') + file_write($config['dir']['home'] . 'debug.xml', $board['dir']); + file_write($config['dir']['home'] . $board['dir'] . $settings['xml'], $this->board_rsspage($settings,$_board)); + } + else { + if ($action == 'all' || $action == 'post' || $action == 'post-thread' || $action == 'post-delete') + file_write($config['dir']['home'] . $board['dir'] . $settings['xml'], $this->board_rsspage($settings,$_board)); + } + } + + } } // Build news page - public function homepage($settings) { + public function site_rsspage($settings) { global $config, $board; - //$recent_images = Array(); $recent_posts = Array(); - //$stats = Array(); $boards = listBoards(); - /*$query = ''; + $query = ''; foreach ($boards as &$_board) { if (in_array($_board['uri'], $this->excluded)) continue; - $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` WHERE `file` IS NOT NULL AND `file` != 'deleted' AND `thumb` != 'spoiler' UNION ALL ", $_board['uri'], $_board['uri']); + if ($settings['show_threads_only'] == 'true'){ + $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` WHERE `thread` is null UNION ALL ", $_board['uri'], $_board['uri']); + } + else { + $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` UNION ALL ", $_board['uri'], $_board['uri']); + } } - $query = preg_replace('/UNION ALL $/', 'ORDER BY `time` DESC LIMIT ' . (int)$settings['limit_images'], $query); + $query = preg_replace('/UNION ALL $/', 'ORDER BY `time` DESC LIMIT ' . (int)$settings['limit_posts'], $query); $query = query($query) or error(db_error()); while ($post = $query->fetch(PDO::FETCH_ASSOC)) { openBoard($post['board']); - // board settings won't be available in the template file, so generate links now $post['link'] = $config['root'] . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], ($post['thread'] ? $post['thread'] : $post['id'])) . '#' . $post['id']; - $post['src'] = $config['uri_thumb'] . $post['thumb']; + $post['snippet'] = pm_snippet($post['body'], 30); + $post['board_name'] = $board['name']; - //$recent_images[] = $post; - }*/ + $recent_posts[] = $post; + } + + + return Element('themes/rss/rss.xml', Array( + 'settings' => $settings, + 'config' => $config, + 'recent_posts' => $recent_posts, + )); + } + + // Build news page + public function board_rsspage($settings,$_board) { + global $config, $board; + $recent_posts = Array(); $query = ''; - foreach ($boards as &$_board) { - if (in_array($_board['uri'], $this->excluded)) - continue; + if ($settings['show_threads_only'] == 'true'){ + $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` WHERE `thread` is null UNION ALL ", $_board['uri'], $_board['uri']); + } + else { $query .= sprintf("SELECT *, '%s' AS `board` FROM ``posts_%s`` UNION ALL ", $_board['uri'], $_board['uri']); } $query = preg_replace('/UNION ALL $/', 'ORDER BY `time` DESC LIMIT ' . (int)$settings['limit_posts'], $query); @@ -77,46 +116,11 @@ $recent_posts[] = $post; } - // Total posts - /*$query = 'SELECT SUM(`top`) FROM ('; - foreach ($boards as &$_board) { - if (in_array($_board['uri'], $this->excluded)) - continue; - $query .= sprintf("SELECT MAX(`id`) AS `top` FROM ``posts_%s`` UNION ALL ", $_board['uri']); - } - $query = preg_replace('/UNION ALL $/', ') AS `posts_all`', $query); - $query = query($query) or error(db_error());*/ - //$stats['total_posts'] = number_format($query->fetchColumn()); - - // Unique IPs - /*$query = 'SELECT COUNT(DISTINCT(`ip`)) FROM ('; - foreach ($boards as &$_board) { - if (in_array($_board['uri'], $this->excluded)) - continue; - $query .= sprintf("SELECT `ip` FROM ``posts_%s`` UNION ALL ", $_board['uri']); - } - $query = preg_replace('/UNION ALL $/', ') AS `posts_all`', $query); - $query = query($query) or error(db_error()); - //$stats['unique_posters'] = number_format($query->fetchColumn());*/ - - // Active content - /*$query = 'SELECT SUM(`filesize`) FROM ('; - foreach ($boards as &$_board) { - if (in_array($_board['uri'], $this->excluded)) - continue; - $query .= sprintf("SELECT `filesize` FROM ``posts_%s`` UNION ALL ", $_board['uri']); - } - $query = preg_replace('/UNION ALL $/', ') AS `posts_all`', $query); - $query = query($query) or error(db_error()); - //$stats['active_content'] = $query->fetchColumn();*/ return Element('themes/rss/rss.xml', Array( 'settings' => $settings, 'config' => $config, - //'boardlist' => createBoardlist(), - //'recent_images' => $recent_images, 'recent_posts' => $recent_posts, - //'stats' => $stats )); } }; -- cgit v1.3 From 2abb80ba296908be3d4480ff32e5eda3822881a6 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 8 Oct 2016 01:47:53 +0900 Subject: Remove debugging from RSS extension --- templates/themes/rss/theme.php | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/themes/rss/theme.php b/templates/themes/rss/theme.php index d0a80f51..eb833ec8 100755 --- a/templates/themes/rss/theme.php +++ b/templates/themes/rss/theme.php @@ -38,7 +38,6 @@ openBoard($_board['uri']); if ($settings['show_threads_only'] == 'true'){ if ($action == 'all' || $action == 'post-thread' || $action == 'post-delete') - file_write($config['dir']['home'] . 'debug.xml', $board['dir']); file_write($config['dir']['home'] . $board['dir'] . $settings['xml'], $this->board_rsspage($settings,$_board)); } else { -- cgit v1.3 From fa01d816b3b2d9dfb4f6b37435d1a6fd3cbf76f7 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 8 Oct 2016 15:29:06 +0900 Subject: Add stream theme / extension to Lainchan --- templates/themes/stream/info.php | 74 +++++++++++++++++++++++++++++++ templates/themes/stream/stream.html | 84 ++++++++++++++++++++++++++++++++++++ templates/themes/stream/theme.php | 34 +++++++++++++++ templates/themes/stream/thumb.png | Bin 0 -> 7872 bytes 4 files changed, 192 insertions(+) create mode 100644 templates/themes/stream/info.php create mode 100644 templates/themes/stream/stream.html create mode 100644 templates/themes/stream/theme.php create mode 100644 templates/themes/stream/thumb.png diff --git a/templates/themes/stream/info.php b/templates/themes/stream/info.php new file mode 100644 index 00000000..169cbe46 --- /dev/null +++ b/templates/themes/stream/info.php @@ -0,0 +1,74 @@ + 'Site title', + 'name' => 'title', + 'type' => 'text' + ); + + $theme['config'][] = Array( + 'title' => 'Slogan', + 'name' => 'subtitle', + 'type' => 'text', + 'comment' => '(optional)' + ); + + $theme['config'][] = Array( + 'title' => 'File', + 'name' => 'file', + 'type' => 'text', + 'default' => 'stream.html', + 'comment' => '(eg. "stream.html")' + ); + $theme['config'][] = Array( + 'title' => 'OGV stream URL', + 'name' => 'ogvurl', + 'type' => 'text', + 'default' => 'https://lainchan.org/radio_assets/lainstream.ogg') + ); + $theme['config'][] = Array( + 'title' => 'RTMP stream URL', + 'name' => 'rtmpurl', + 'type' => 'text', + 'default' => 'rtmp://lainchan.org/live/&stream') + ); + + $theme['config'][] = Array( + 'title' => 'OGV Status URL', + 'name' => 'ogvstatus', + 'type' => 'text', + 'default' => '/radio_assets/status.xsl') + ); + + $theme['config'][] = Array( + 'title' => 'RTMP Status URL', + 'name' => 'rtmpstatus', + 'type' => 'text', + 'default' => '/live/status?app=live&name=stream') + ); + $theme['config'][] = Array( + 'title' => 'RTMP Viewers URL', + 'name' => 'rtmpviewers', + 'type' => 'text', + 'default' => '/live/subs?app=live&name=stream') + ); + + + // Unique function name for building everything + $theme['build_function'] = 'stream_build'; + $theme['install_callback'] = 'stream_install'; + if (!function_exists('stream_install')) { + function stream_install($settings) { + } + } + diff --git a/templates/themes/stream/stream.html b/templates/themes/stream/stream.html new file mode 100644 index 00000000..cf952366 --- /dev/null +++ b/templates/themes/stream/stream.html @@ -0,0 +1,84 @@ +{% filter remove_whitespace %} + + + + + + + {{ settings.title }} + {% if config.meta_keywords %}{% endif %} + + {% if config.url_favicon %}{% endif %} + + + {% if config.font_awesome %}{% endif %} + + + + + +
    + {{ boardlist.top }} +
    +
    +

    {{ settings.title }}

    +
    {{ settings.subtitle }}
    +
    + +
    + + +
    +

    Now Streaming RTMP unknown

    +

    Now Streaming OGV unknown

    +

    Current RTMP viewers: unknown

    +

    Current OGV viewers: unknown

    +
    +

    Raw stream URLs for Mplayer, mpv, VLC, etc:

    +

    RTMP : {{ setings.rtmpurl }}

    +

    OGV : {{ setings.ogvurl }}

    +

    +
    + +
    + +
    +
    +

    - Tinyboard + + vichan {{ config.version }} - +
    Tinyboard Copyright © 2010-2014 Tinyboard Development Group +
    vichan Copyright © 2012-2015 vichan-devel

    +
    + + + +{% endfilter %} diff --git a/templates/themes/stream/theme.php b/templates/themes/stream/theme.php new file mode 100644 index 00000000..6d11a0dc --- /dev/null +++ b/templates/themes/stream/theme.php @@ -0,0 +1,34 @@ + $settings, + 'config' => $config, + 'boardlist' => createBoardlist(), + )); + } + }; + +?> diff --git a/templates/themes/stream/thumb.png b/templates/themes/stream/thumb.png new file mode 100644 index 00000000..d0c3c771 Binary files /dev/null and b/templates/themes/stream/thumb.png differ -- cgit v1.3 From bd3cc259810fa664abd09de0d94e4d0dc70648e8 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 10 Dec 2016 18:15:11 +0900 Subject: Add JSON API to Ukko Overboard Theme --- templates/themes/ukko/theme.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/templates/themes/ukko/theme.php b/templates/themes/ukko/theme.php index ee2707bf..a3f30dd4 100644 --- a/templates/themes/ukko/theme.php +++ b/templates/themes/ukko/theme.php @@ -15,6 +15,10 @@ if ($action == 'delete') { file_unlink($settings['uri'] . '/index.html'); + if ($config['api']['enabled']) { + $jsonFilename = $settings['uri'] . '/0.json'; + file_unlink($jsonFilename); + } } elseif ($action == 'rebuild') { file_write($settings['uri'] . '/index.html', $ukko->build()); @@ -48,6 +52,9 @@ $count = 0; $threads = array(); + if ($config['api']['enabled']) { + $apithreads = array(); + } while($post = $query->fetch()) { if(!isset($threads[$post['board']])) { @@ -89,6 +96,9 @@ $thread->posts = array_reverse($thread->posts); $body .= '

    /' . $post['board'] . '/

    '; $body .= $thread->build(true); + if ($config['api']['enabled']) { + array_push($apithreads,$thread); + } } else { $page = 'index'; if(floor($threads[$post['board']] / $config['threads_per_page']) > 0) { @@ -102,7 +112,15 @@ $body .= ''; $body .= ''; - + + // json api + if ($config['api']['enabled']) { + require_once __DIR__. '/../../../inc/api.php'; + $api = new Api(); + $jsonFilename = $board['dir'] . '0.json'; + $json = json_encode($api->translatePage($apithreads)); + file_write($jsonFilename, $json); + } return Element('index.html', array( 'config' => $config, 'board' => $board, -- cgit v1.3 From 1c7c6458316daaed5b233e1c125a7aa2f853425e Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 10 Dec 2016 18:18:27 +0900 Subject: Adding separate videojs RTMP URL configuration variable to stream theme --- templates/themes/stream/info.php | 16 +++++++++++----- templates/themes/stream/stream.html | 18 +++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/templates/themes/stream/info.php b/templates/themes/stream/info.php index 169cbe46..081b29ca 100644 --- a/templates/themes/stream/info.php +++ b/templates/themes/stream/info.php @@ -34,33 +34,39 @@ 'title' => 'OGV stream URL', 'name' => 'ogvurl', 'type' => 'text', - 'default' => 'https://lainchan.org/radio_assets/lainstream.ogg') + 'default' => 'https://lainchan.org/radio_assets/lainstream.ogg' ); $theme['config'][] = Array( 'title' => 'RTMP stream URL', 'name' => 'rtmpurl', 'type' => 'text', - 'default' => 'rtmp://lainchan.org/live/&stream') + 'default' => 'rtmp://lainchan.org/live/stream' + ); + $theme['config'][] = Array( + 'title' => 'RTMP Video.JS stream URL', + 'name' => 'rtmpvideojsurl', + 'type' => 'text', + 'default' => 'rtmp://lainchan.org/live/&stream' ); $theme['config'][] = Array( 'title' => 'OGV Status URL', 'name' => 'ogvstatus', 'type' => 'text', - 'default' => '/radio_assets/status.xsl') + 'default' => '/radio_assets/status.xsl' ); $theme['config'][] = Array( 'title' => 'RTMP Status URL', 'name' => 'rtmpstatus', 'type' => 'text', - 'default' => '/live/status?app=live&name=stream') + 'default' => '/live/status?app=live&name=stream' ); $theme['config'][] = Array( 'title' => 'RTMP Viewers URL', 'name' => 'rtmpviewers', 'type' => 'text', - 'default' => '/live/subs?app=live&name=stream') + 'default' => '/live/subs?app=live&name=stream' ); diff --git a/templates/themes/stream/stream.html b/templates/themes/stream/stream.html index cf952366..dc2ed26d 100644 --- a/templates/themes/stream/stream.html +++ b/templates/themes/stream/stream.html @@ -12,6 +12,9 @@ {% if config.font_awesome %}{% endif %} + + {{ settings.title }} ( /{{ board }}/ ) {% include 'header.html' %} @@ -41,7 +42,7 @@
    - +

    {% for post in recent_posts %} diff --git a/templates/themes/catalog/theme.php b/templates/themes/catalog/theme.php index 24cdcbb8..3ef974c3 100644 --- a/templates/themes/catalog/theme.php +++ b/templates/themes/catalog/theme.php @@ -15,7 +15,7 @@ // - post-thread (a thread has been made) if ($action === 'all') { foreach ($boards as $board) { - $b = new Catalog(); + $b = new Catalog($settings); $action = generation_strategy("sb_catalog", array($board)); if ($action == 'delete') { @@ -27,7 +27,7 @@ } } } elseif ($action == 'post-thread' || ($settings['update_on_posts'] && $action == 'post') || ($settings['update_on_posts'] && $action == 'post-delete') && in_array($board, $boards)) { - $b = new Catalog(); + $b = new Catalog($settings); $action = generation_strategy("sb_catalog", array($board)); if ($action == 'delete') { @@ -148,10 +148,13 @@ /** * Build and save the HTML of the catalog for the given board */ - public function build($board_name) { - if (!openBoard($board_name)) { - error(sprintf(_("Board %s doesn't exist"), $post['board'])); - } + public function build($settings, $board_name) { + global $config, $board; + if ($board['uri'] != $board_name) { + if (!openBoard($board_name)) { + error(sprintf(_("Board %s doesn't exist"), $board_name)); + } + } if (array_key_exists($board_name, $this->threadsCache)) { $threads = $this->threadsCache[$board_name]; -- cgit v1.3 From b89fe3046bfbc0d7db8e7e8773e92c0536417450 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 10 Dec 2016 18:39:18 +0900 Subject: Fix board filter listing in the search form --- templates/search_form.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/templates/search_form.html b/templates/search_form.html index 4c8bd077..095968de 100644 --- a/templates/search_form.html +++ b/templates/search_form.html @@ -6,8 +6,13 @@ -- cgit v1.3 From 2c9fbb5770495a209872642fb3169d8568e4181e Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sat, 10 Dec 2016 18:40:11 +0900 Subject: Updated maximum file size on rules page --- templates/rules.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/rules.html b/templates/rules.html index 76fb6a5f..3cc064c2 100644 --- a/templates/rules.html +++ b/templates/rules.html @@ -24,7 +24,7 @@
  • Mods usually ask each other for opinions before doing anything. We hate having to ban someone.
  • Just relax and try to have fun. Jokes are always welcome.
  • Visit a board you've never been to. Try out /random/.
  • -
  • Max file size is 35 mb.
  • +
  • Max file size is 25 mb.
  • PDF, ePub, and webm are supported across the boards.
  • We ALWAYS need contributors to our board software, located on github
  • Dice rolling: posting in the email field with the form "dice XdY+/-Z" will result in X Y-sided dice rolled and summed, with the modifier Z added, the result is displayed at the top of the post body.
  • -- cgit v1.3 From ceb8df370caeba48219530bf5deb5e6a06e56648 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Sun, 11 Dec 2016 08:04:18 +0900 Subject: Fix copyright on page templates --- templates/generic_page.html | 11 +++++++---- templates/page.html | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/templates/generic_page.html b/templates/generic_page.html index 6d2f28c3..cc076106 100644 --- a/templates/generic_page.html +++ b/templates/generic_page.html @@ -38,10 +38,13 @@ {% endfor %} {{ btn.next }}
    {{ boardlist.bottom }}
    -

    - Tinyboard + - vichan {{ config.version }} - -
    Tinyboard Copyright © 2010-2014 Tinyboard Development Group -
    vichan Copyright © 2012-2016 vichan-devel

    +

    - Tinyboard + + vichan + + lainchan {{ config.version }} - +
    Tinyboard Copyright © 2010-2014 Tinyboard Development Group +
    vichan Copyright © 2012-2016 vichan-devel +
    lainchan Copyright © 2014-2016 lainchan Administration

    + {% for footer in config.footer %}

    {{ footer }}

    {% endfor %}