diff --git a/404.php b/404.php index b660857f..e74eb65b 100644 --- a/404.php +++ b/404.php @@ -32,7 +32,10 @@ if (preg_match('!'.$config['board_regex'].'/'.$config['dir']['res'].'\d+\.html!u $return_link = ''; } +$ad = Element("ad_top.html", array()); + $page = <<$ad

[ Home ]{$return_link}

diff --git a/claim.php b/claim.php index b1b155c6..456c8a32 100644 --- a/claim.php +++ b/claim.php @@ -31,7 +31,7 @@ function last_activity($board) { } else {// no one ever logged in, try board creation time $query = query("SELECT UNIX_TIMESTAMP(time) AS time FROM board_create WHERE uri = '$board'"); $crt = $query->fetchAll(PDO::FETCH_COLUMN); - $last_activity_date->setTimestamp($crt[0]); + if ($crt) $last_activity_date->setTimestamp($crt[0]); $last_mod_date = false; } } diff --git a/dnsbls_bypass.php b/dnsbls_bypass.php new file mode 100644 index 00000000..a37f1ca0 --- /dev/null +++ b/dnsbls_bypass.php @@ -0,0 +1,45 @@ + array('8.8.8.8'))); +$result = $dns->query(RECAPTCHA_VERIFY_SERVER, "A"); +if ($result and $result->answer[0]) { + $RECAPTCHA_VERIFY_SERVER_IP = $result->answer[0]->address; +} else { + $RECAPTCHA_VERIFY_SERVER_IP = RECAPTCHA_VERIFY_SERVER; +} + +if ($_SERVER['REQUEST_METHOD'] === 'GET') { + $ayah_html = recaptcha_get_html($config['recaptcha_public'], NULL, TRUE); + $body = Element("8chan/dnsbls.html", array("config" => $config, "ayah_html" => $ayah_html)); + + echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Bypass DNSBL"), "subtitle" => _("Post even if blocked"))); +} else { + $score = recaptcha_check_answer($config['recaptcha_private'], + $_SERVER["REMOTE_ADDR"], + $_POST["recaptcha_challenge_field"], + $_POST["recaptcha_response_field"], + array(), + $RECAPTCHA_VERIFY_SERVER_IP); + + if ($score->is_valid) { + $tor = checkDNSBL($_SERVER['REMOTE_ADDR']); + if (!$tor) { + $query = prepare('INSERT INTO ``dnsbl_bypass`` VALUES(:ip, NOW()) ON DUPLICATE KEY UPDATE `created`=NOW()'); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->execute() or error(db_error($query)); + } else { + $cookie = bin2hex(openssl_random_pseudo_bytes(16)); + $query = prepare('INSERT INTO ``tor_cookies`` VALUES(:cookie, NOW(), 0)'); + $query->bindValue(':cookie', $cookie); + $query->execute() or error(db_error($query)); + setcookie("tor", $cookie); + } + echo Element("page.html", array("config" => $config, "body" => '', "title" => _("Success!"), "subtitle" => _("You may now go back and make your post."))); + } else { + error(_('You failed the CAPTCHA') . _('. Try again. If it\'s not working, email admin@8chan.co for support.')); + } +} diff --git a/faq.php b/faq.php index 75448146..d01befae 100644 --- a/faq.php +++ b/faq.php @@ -79,8 +79,9 @@ $body = <<__underline__ -> underline
  • ==heading== -> heading (must be on own line)
  • ~~strikethrough~~ -> strikethrough
  • +
  • [aa] tags for ASCII/JIS art (escape formatting)
  • [code] tags if enabled by board owner
  • -
  • [tex] tags if enabled by board owner
  • +
  • [tex] tags if enabled by board owner (currently globally disabled)
  • How are featured boards chosen?

    @@ -110,10 +111,14 @@ $body = <<claim at 8chan dot co +

    I would like to send you an encrypted message.

    +

    The current admin contact private key can always be found at https://8ch.net/pubkey.txt.

    +

    The current key fingerprint is 6F12 EC72 A82A BCA3 5235 063A 10DD C983 901A A183.

    +

    How do I donate?

    Donations can be sent to 1NpQaXqmCBji6gfX8UgaQEmEstvVY7U32C (Bitcoin) or LUPgSCJt3iGeJXUETVhmnbQ89Riaq1yjZm (Litecoin).

    I am also a big fan of Monero (XMR). You can send XMR to our OpenAlias in the simplewallet client, or simply send to 49dBJhGhYFxJEfydS6hH6GRyg1W4cDgupdNVtw7j1WtcUY7xPXwNLw6fUVay644viaCcEhMFG1Z7SjjxRXEFDdNWJdvH9kS.

    -

    You may also donate monthly via Patreon at http://www.patreon.com/user?u=162165. +

    If you would like to support development of the engine that 8chan runs on (infinity), you may also donate via Gratipay.

    Are you really a cripple?

    Yes.

    diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index 92a1f2e0..779a4789 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -14,6 +14,28 @@ } } + if (!function_exists('is_billion_laughs')){ + function is_billion_laughs($arr1, $arr2) { + $arr = array(); + foreach ($arr1 as $k => $v) { + $arr[$v] = $arr2[$k]; + } + + for ($i = 0; $i <= sizeof($arr); $i++) { + $cur = array_slice($arr, $i, 1); + $pst = array_slice($arr, 0, $i); + if (!$cur) continue; + $kk = array_keys($cur)[0]; + $vv = array_values($cur)[0]; + foreach ($pst as $k => $v) { + if (str_replace($kk, $vv, $v) != $v) + return true; + } + } + return false; + } + } + $config['mod']['show_ip'] = GLOBALVOLUNTEER; $config['mod']['show_ip_less'] = BOARDVOLUNTEER; $config['mod']['manageusers'] = GLOBALVOLUNTEER; @@ -27,6 +49,7 @@ $config['mod']['debug_antispam'] = ADMIN; $config['mod']['noticeboard_post'] = ADMIN; $config['mod']['modlog'] = GLOBALVOLUNTEER; + $config['mod']['mod_board_log'] = MOD; $config['mod']['editpost'] = BOARDVOLUNTEER; $config['mod']['edit_banners'] = MOD; $config['mod']['edit_flags'] = MOD; @@ -54,6 +77,8 @@ $config['mod']['view_ban_appeals'] = BOARDVOLUNTEER; $config['mod']['view_ban'] = BOARDVOLUNTEER; $config['mod']['reassign_board'] = ADMIN; + $config['mod']['move'] = GLOBALVOLUNTEER; + $config['mod']['shadow_capcode'] = 'Global Volunteer'; $config['mod']['custom_pages']['/tags/(\%b)'] = function ($b) { global $board, $config; @@ -408,7 +433,7 @@ FLAGS; $user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : ''; $captcha = isset($_POST['captcha']) ? 'true' : 'false'; $force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false'; - /*New thread captcha*/ + $tor_posting = isset($_POST['tor_posting']) ? 'true' : 'false'; $new_thread_capt = isset($_POST['new_thread_capt']) ? 'true' : 'false'; @@ -461,6 +486,9 @@ OEKAKI; } } } + if (is_billion_laughs($_POST['replace'], $_POST['with'])) { + error(_('Wordfilters may not wordfilter previous wordfilters. For example, if a filters to bb and b filters to cc, that is not allowed.')); + } } if (isset($_POST['hour_max_threads']) && in_array($_POST['hour_max_threads'], ['10', '25', '50', '100'])) { @@ -504,7 +532,7 @@ OEKAKI; \$config['default_stylesheet'] = array('Custom', \$config['stylesheets']['Custom']); \$config['captcha']['enabled'] = $captcha; \$config['force_subject_op'] = $force_subject_op; -/*New thread captcha*/ +\$config['tor_posting'] = $tor_posting; \$config['new_thread_capt'] = $new_thread_capt; \$config['hour_max_threads'] = $hour_max_threads; $code_tags $katex $oekaki $replace $multiimage $allow_flash $allow_pdf $user_flags @@ -518,8 +546,7 @@ EOT; // Clean up our CSS...no more expression() or off-site URLs. $clean_css = preg_replace('/expression\s*\(/', '', $_POST['css']); - // URL matcher from SO: - $match_urls = '(?xi)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))'; + $match_urls = '((?:(?:https?:)?\/\/|ftp:\/\/|irc:\/\/)[^\s<>()"]+?(?:\([^\s<>()"]*?\)[^\s<>()"]*?)*)((?:\s|<|>|"|\.|\]|!|\?|,|&\#44;|")*(?:[\s<>()"]|$))'; $matched = array(); @@ -529,7 +556,7 @@ EOT; foreach ($matched[0] as $match) { $match_okay = false; foreach ($allowed_urls as $allowed_url) { - if (strpos($match, $allowed_url) !== false) { + if (strpos($match, $allowed_url) !== false && strpos($match, '#') === false) { $match_okay = true; } } @@ -540,10 +567,9 @@ EOT; } //Filter out imports from sites with potentially unsafe content - $css_no_comments = preg_replace('|\/\*.*\*\/|', '', $clean_css); //I can't figure out how to ignore comments in the match $match_imports = '@import[^;]*'; $matched = array(); - preg_match_all("#$match_imports#im", $css_no_comments, $matched); + preg_match_all("#$match_imports#im", $clean_css, $matched); $unsafe_import_urls = array('https://a.pomf.se/'); @@ -551,7 +577,7 @@ EOT; foreach ($matched[0] as $match) { $match_okay = true; foreach ($unsafe_import_urls as $unsafe_import_url) { - if (strpos($match, $unsafe_import_url) !== false) { + if (strpos($match, $unsafe_import_url) !== false && strpos($match, '#') === false) { $match_okay = false; } } diff --git a/inc/api.php b/inc/api.php index bc271188..9329fc4d 100644 --- a/inc/api.php +++ b/inc/api.php @@ -102,7 +102,7 @@ class Api { $fields = $threadsPage ? $this->threadsPageFields : $this->postFields; $this->translateFields($fields, $post, $apiPost); - //if (isset($config['poster_ids'])) $apiPost['id'] = poster_id($post->ip, $post->thread, $board['uri']); + if ($this->config['poster_ids']) $apiPost['id'] = poster_id($post->ip, $post->thread, $board['uri']); if ($threadsPage) return $apiPost; // Handle country field diff --git a/inc/bans.php b/inc/bans.php index e0866ed4..b2c001f5 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -156,17 +156,18 @@ class Bans { static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) { global $config, $pdo; - if ($board_access && $board_access[0] == '*') $board_access = false; $query_addition = ""; - if ($board_access !== FALSE) { - $query_addition .= "WHERE `public_bans` OR `public_bans` IS NULL"; - } if ($board_access) { $boards = implode(", ", array_map(array($pdo, "quote"), $board_access)); - $query_addition .= " OR `board` IN (".$boards.")"; + $query_addition .= "WHERE `board` IN (".$boards.")"; + } + if ($board_access !== FALSE) { + if (!$query_addition) { + $query_addition .= " WHERE (`public_bans` IS TRUE)"; + } } $query = query("SELECT ``bans``.*, `username`, `type` FROM ``bans`` diff --git a/inc/config.php b/inc/config.php index 42b40336..34d3d1da 100644 --- a/inc/config.php +++ b/inc/config.php @@ -559,7 +559,7 @@ $config['field_disable_password'] = false; // When true, users are instead presented a selectbox for email. Contains, blank, noko and sage. - $config['field_email_selectbox'] = false; + $config['field_email_selectbox'] = &$config['field_disable_name']; // When true, the sage won't be displayed $config['hide_sage'] = false; @@ -1110,7 +1110,7 @@ $config['error']['webmerror'] = _('There was a problem processing your webm.');//Is this error used anywhere ? $config['error']['invalidwebm'] = _('Invalid webm uploaded.'); $config['error']['webmhasaudio'] = _('The uploaded webm contains an audio or another type of additional stream.'); - $config['error']['webmtoolong'] = _('The uploaded webm is longer than ' . $config['webm']['max_length'] . ' seconds.'); + $config['error']['webmtoolong'] = _('The uploaded webm is longer than %d seconds.'); $config['error']['fileexists'] = _('That file already exists!'); $config['error']['fileexistsinthread'] = _('That file already exists in this thread!'); $config['error']['delete_too_soon'] = _('You\'ll have to wait another %s before deleting that.'); diff --git a/inc/display.php b/inc/display.php index 89e32fed..bd180f5a 100644 --- a/inc/display.php +++ b/inc/display.php @@ -73,6 +73,9 @@ function createBoardlist($mod=false) { function error($message, $priority = true, $debug_stuff = false) { global $board, $mod, $config, $db_error; + + if (isset($debug_stuff['file'])) + $message .= " {$debug_stuff['file']}"; if ($config['syslog'] && $priority !== false) { // Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant. diff --git a/inc/functions.php b/inc/functions.php index afb7d522..57d91cbb 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1050,19 +1050,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)); @@ -1580,20 +1583,22 @@ function buildJavascript() { file_write($config['file_script'], $script); } -function checkDNSBL() { +function checkDNSBL($use_ip = false) { global $config; - - if (isIPv6()) - return; // No IPv6 support yet. - - if (!isset($_SERVER['REMOTE_ADDR'])) + if (!$use_ip && !isset($_SERVER['REMOTE_ADDR'])) return; // Fix your web server configuration - if (in_array($_SERVER['REMOTE_ADDR'], $config['dnsbl_exceptions'])) + $ip = ($use_ip ? $use_ip : $_SERVER['REMOTE_ADDR']); + if ($ip == '127.0.0.2') return true; + + if (isIPv6($ip)) + return; // No IPv6 support yet. + + if (in_array($ip, $config['dnsbl_exceptions'])) return; - $ipaddr = ReverseIPOctets($_SERVER['REMOTE_ADDR']); + $ipaddr = ReverseIPOctets($ip); foreach ($config['dnsbl'] as $blacklist) { if (!is_array($blacklist)) @@ -1609,24 +1614,31 @@ function checkDNSBL() { if (!isset($blacklist[1])) { // If you're listed at all, you're blocked. - error(sprintf($config['error']['dnsbl'], $blacklist_name)); + if ($use_ip) { + return true; + } else { + error(sprintf($config['error']['dnsbl'], $blacklist_name)); + } } elseif (is_array($blacklist[1])) { foreach ($blacklist[1] as $octet) { - if ($ip == $octet || $ip == '127.0.0.' . $octet) - error(sprintf($config['error']['dnsbl'], $blacklist_name)); + if ($ip == $octet || $ip == '127.0.0.' . $octet) { + return true; + } } } elseif (is_callable($blacklist[1])) { - if ($blacklist[1]($ip)) - error(sprintf($config['error']['dnsbl'], $blacklist_name)); + if ($blacklist[1]($ip)) { + return true; + } } else { - if ($ip == $blacklist[1] || $ip == '127.0.0.' . $blacklist[1]) - error(sprintf($config['error']['dnsbl'], $blacklist_name)); + if ($ip == $blacklist[1] || $ip == '127.0.0.' . $blacklist[1]) { + return true; + } } } } -function isIPv6() { - return strstr($_SERVER['REMOTE_ADDR'], ':') !== false; +function isIPv6($ip = false) { + return strstr(($ip ? $ip : $_SERVER['REMOTE_ADDR']), ':') !== false; } function ReverseIPOctets($ip) { @@ -1816,7 +1828,7 @@ function markup(&$body, $track_cites = false, $op = false) { } if (isset($cited_posts[$cite])) { - $replacement = '' . '>>' . $cite . @@ -1915,7 +1927,7 @@ function markup(&$body, $track_cites = false, $op = false) { $replacement = '' . '>>>/' . $_board . '/' . $cite . ''; @@ -2216,7 +2228,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}"]; diff --git a/inc/instance-config.php b/inc/instance-config.php index fba06740..d89c8716 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -51,7 +51,7 @@ require "secrets.php"; // Image shit - $config['thumb_method'] = 'gm'; + $config['thumb_method'] = 'convert'; $config['thumb_ext'] = 'jpg'; $config['thumb_keep_animation_frames'] = 1; $config['show_ratio'] = true; @@ -64,7 +64,7 @@ $config['allowed_ext_files'][] = 'mp4'; $config['webm']['use_ffmpeg'] = true; $config['webm']['allow_audio'] = true; - $config['webm']['max_length'] = 60 * 15; + $config['webm']['max_length'] = 60 * 30; // Mod shit $config['mod']['groups'][25] = 'GlobalVolunteer'; @@ -103,7 +103,6 @@ $config['additional_javascript'][] = 'js/update_boards.js'; $config['additional_javascript'][] = 'js/favorites.js'; $config['additional_javascript'][] = 'js/show-op.js'; - $config['additional_javascript'][] = 'js/hide-threads.js'; $config['additional_javascript'][] = 'js/smartphone-spoiler.js'; $config['additional_javascript'][] = 'js/inline-expanding.js'; $config['additional_javascript'][] = 'js/show-backlinks.js'; @@ -143,11 +142,14 @@ $config['additional_javascript'][] = 'js/quote-selection.js'; $config['additional_javascript'][] = 'js/twemoji/twemoji.js'; $config['additional_javascript'][] = 'js/flag-previews.js'; + $config['additional_javascript'][] = 'js/post-filter.js'; + $config['additional_javascript'][] = 'js/image-hover.js'; //$config['font_awesome_css'] = '/netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css'; $config['stylesheets']['Dark'] = 'dark.css'; $config['stylesheets']['Photon'] = 'photon.css'; + $config['stylesheets']['Redchanit'] = 'redchanit.css'; $config['stylesheets_board'] = true; $config['markup'][] = array("/^[ |\t]*==(.+?)==[ |\t]*$/m", "\$1"); @@ -156,7 +158,7 @@ $config['markup'][] = array("/__(.+?)__/", "\$1"); $config['markup'][] = array("/###([^\s']+)###/", "###\$1###"); - $config['boards'] = array(array('' => '/', '' => '/boards.html', '' => '/faq.html', '' => '/random.php', '' => '/create.php', '' => '/bans.html', '' => '/search.php', '' => '/mod.php', '' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'news+'), array(''=>'https://twitter.com/infinitechan')); + $config['boards'] = array(array('' => '/', '' => '/boards.html', '' => '/faq.html', '' => '/random.php', '' => '/create.php', '' => '/bans.html', '' => '/search.php', '' => '/mod.php', '' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'news+', 'boards'), array('operate', 'meta'), array(''=>'https://twitter.com/infinitechan')); //$config['boards'] = array(array('' => '/', '' => '/boards.html', '' => '/faq.html', '' => '/random.php', '' => '/create.php', '' => '/search.php', '' => '/mod.php', '' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'int'), array('v', 'a', 'tg', 'fit', 'pol', 'tech', 'mu', 'co', 'sp', 'boards'), array(''=>'https://twitter.com/infinitechan')); $config['footer'][] = 'All posts on 8chan are the responsibility of the individual poster and not the administration of 8chan, pursuant to 47 U.S.C. § 230.'; diff --git a/inc/lib/webm/ffmpeg.php b/inc/lib/webm/ffmpeg.php index 485e4702..f867c8db 100644 --- a/inc/lib/webm/ffmpeg.php +++ b/inc/lib/webm/ffmpeg.php @@ -50,7 +50,7 @@ function is_valid_webm($ffprobe_out) { return array('code' => 2, 'msg' => $config['error']['invalidwebm']); if ($ffprobe_out['format']['duration'] > $config['webm']['max_length']) - return array('code' => 4, 'msg' => $config['error']['webmtoolong']); + return array('code' => 4, 'msg' => sprintf($config['error']['webmtoolong'], $config['webm']['max_length'])); } function make_webm_thumbnail($filename, $thumbnail, $width, $height, $duration) { diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 93e076f9..44c69695 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -639,7 +639,7 @@ function mod_log($page_no = 1) { $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count)); + mod_page(_('Board log'), 'mod/log.html', array('logs' => $logs, 'count' => $count)); } function mod_user_log($username, $page_no = 1) { @@ -666,7 +666,43 @@ function mod_user_log($username, $page_no = 1) { $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'username' => $username)); + mod_page(_('Board log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'username' => $username)); +} + +function mod_board_log($board, $page_no = 1) { + global $config; + + if ($page_no < 1) + error($config['error']['404']); + + if (!hasPermission($config['mod']['mod_board_log'], $board)) + 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 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)); } function mod_view_board($boardName, $page_no = 1) { @@ -1361,8 +1397,10 @@ function mod_move($originBoard, $postID) { if ($post['has_file']) { // copy image foreach ($post['files'] as $i => &$file) { - $clone($file['file_path'], sprintf($config['board_path'], $config['dir']['img_root'] . $board['uri']) . $config['dir']['img'] . $file['file']); - $clone($file['thumb_path'], sprintf($config['board_path'], $config['dir']['img_root'] . $board['uri']) . $config['dir']['thumb'] . $file['thumb']); + if ($file['file'] !== 'deleted') + $clone($file['file_path'], sprintf($config['board_path'], $config['dir']['img_root'] . $board['uri']) . $config['dir']['img'] . $file['file']); + if (isset($file['thumb']) && !in_array($file['thumb'], array('spoiler', 'deleted', 'file'))) + $clone($file['thumb_path'], sprintf($config['board_path'], $config['dir']['img_root'] . $board['uri']) . $config['dir']['thumb'] . $file['thumb']); } } // insert reply @@ -1464,6 +1502,7 @@ function mod_ban_post($board, $delete, $post, $token = false) { $thread = $_post['thread']; $ip = $_post['ip']; + $tor = checkDNSBL($ip); if (isset($_POST['new_ban'], $_POST['reason'], $_POST['length'], $_POST['board'])) { require_once 'inc/mod/ban.php'; @@ -1510,6 +1549,7 @@ function mod_ban_post($board, $delete, $post, $token = false) { 'hide_ip' => !hasPermission($config['mod']['show_ip'], $board), 'post' => $post, 'board' => $board, + 'tor' => $tor, 'delete' => (bool)$delete, 'boards' => listBoards(), 'token' => $security_token @@ -1837,6 +1877,9 @@ function mod_user($uid) { if (!hasPermission($config['mod']['editusers']) && !(hasPermission($config['mod']['change_password']) && $uid == $mod['id'])) error($config['error']['noaccess']); + + if (in_array($mod['boards'][0], array('infinity', 'z'))) + error('This board has password changing disabled.'); $query = prepare('SELECT * FROM ``mods`` WHERE `id` = :id'); $query->bindValue(':id', $uid); diff --git a/install.sql b/install.sql index 9a03736f..e76dbe00 100644 --- a/install.sql +++ b/install.sql @@ -331,6 +331,31 @@ CREATE TABLE `board_tags` ( PRIMARY KEY (`id`) ); +-- -------------------------------------------------------- + +-- +-- Table structure for table `tor_cookies` +-- + +CREATE TABLE `tor_cookies` ( + `cookie` varchar(255) NOT NULL, + `created` datetime NOT NULL, + `uses` tinyint(3) unsigned DEFAULT '0', + PRIMARY KEY (`cookie`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `dnsbl_bypass` +-- + +CREATE TABLE `dnsbl_bypass` ( + `ip` varchar(255) NOT NULL, + `created` datetime DEFAULT NULL, + PRIMARY KEY (`ip`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + /*!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/js/catalog-search.js b/js/catalog-search.js index 17a8edac..ff9af785 100644 --- a/js/catalog-search.js +++ b/js/catalog-search.js @@ -34,16 +34,16 @@ if (active_page == 'catalog') { } function searchToggle() { - var button = $('#catalog_search_button')[0]; + var button = $('#catalog_search_button'); - if (!button.dataset.expanded) { - button.dataset.expanded = '1'; - button.innerText = 'Close'; + if (!button.data('expanded')) { + button.data('expanded', '1'); + button.text('Close'); $('.catalog_search').append(' '); $('#search_field').focus(); } else { - delete button.dataset.expanded; - button.innerText = 'Search'; + button.removeData('expanded'); + button.text('Search'); $('.catalog_search').children().last().remove(); $('div[id="Grid"]>.mix').each(function () { $(this).css('display', 'inline-block'); }); } diff --git a/js/catalog.js b/js/catalog.js index 801c86bc..38382096 100644 --- a/js/catalog.js +++ b/js/catalog.js @@ -1,5 +1,4 @@ if (active_page == 'catalog') $(function(){ - if (localStorage.catalog !== undefined) { var catalog = JSON.parse(localStorage.catalog); } else { @@ -15,8 +14,18 @@ if (active_page == 'catalog') $(function(){ $('a[href$="/'+k+'.html"]').parents('.mix').remove(); }); } + } else { + hidden_data = {}; } + $(document).on('click', '.mix', function(e) { + if (e.shiftKey) { + hidden_data[board_name][$(this).data('id')] = Math.round(Date.now() / 1000); + $(this).remove(); + localStorage.hiddenthreads = JSON.stringify(hidden_data); + } + }); + $("#sort_by").change(function(){ var value = this.value; $('#Grid').mixItUp('sort', value); @@ -46,5 +55,4 @@ if (active_page == 'catalog') $(function(){ if (catalog.image_size !== undefined) { $('#image_size').val(catalog.image_size).trigger('change'); } - }); diff --git a/js/expand-all-images.js b/js/expand-all-images.js index 6d7b4ca0..c110f51c 100644 --- a/js/expand-all-images.js +++ b/js/expand-all-images.js @@ -31,7 +31,7 @@ onready(function(){ if (/^\/player\.php\?/.test($(this).parent().attr('href'))) return; - if (!$(this).parent()[0].dataset.expanded) + if (!$(this).parent().data('expanded')) $(this).parent().click(); }); @@ -43,7 +43,7 @@ onready(function(){ .text(_('Shrink all images')) .click(function(){ $('a img.full-image').each(function() { - if ($(this).parent()[0].dataset.expanded) + if ($(this).parent().data('expanded')) $(this).parent().click(); }); $(this).parent().remove(); diff --git a/js/favorites.js b/js/favorites.js index e4d89086..acef9c7e 100644 --- a/js/favorites.js +++ b/js/favorites.js @@ -41,15 +41,15 @@ function handle_boards(data) { return $('').append(' [ '+boards.join(" / ")+' ] '); } else { return $(''); - } + } } function add_favorites() { - $('.favorite-boards').empty(); - + $('.favorite-boards').remove(); + var boards = handle_boards(localStorage.favorites); - $('.favorite-boards').append(boards); + $('.boardlist').append(boards); }; if (active_page == 'thread' || active_page == 'index' || active_page == 'catalog') { diff --git a/js/hide-images.js b/js/hide-images.js index 023c166c..7b21d638 100644 --- a/js/hide-images.js +++ b/js/hide-images.js @@ -77,7 +77,7 @@ $(document).ready(function(){ $(this).hide().after(show_link); - if ($(img).parent()[0].dataset.expanded == 'true') { + if ($(img).parent().data('expanded') == 'true') { $(img).parent().click(); } diff --git a/js/id_colors.js b/js/id_colors.js index 17f8b4d9..6335f960 100644 --- a/js/id_colors.js +++ b/js/id_colors.js @@ -1,18 +1,18 @@ if (active_page == 'thread' || active_page == 'index') { $(document).ready(function(){ if (window.Options && Options.get_tab('general')) { - selector = '#color-ids>input'; - event = 'change'; + var selector = '#color-ids>input'; + var e = 'change'; Options.extend_tab("general", ""); } else { - selector = '#color-ids'; - event = 'click'; + var selector = '#color-ids'; + var e = 'click'; $('hr:first').before('') } - $(selector).on(event, function() { + $(selector).on(e, function() { if (localStorage.color_ids === 'true') { localStorage.color_ids = 'false'; } else { diff --git a/js/image-hover.js b/js/image-hover.js new file mode 100644 index 00000000..801b8dff --- /dev/null +++ b/js/image-hover.js @@ -0,0 +1,186 @@ +/* image-hover.js + * This script is copied almost verbatim from https://github.com/Pashe/8chanX/blob/2-0/8chan-x.user.js + * All I did was remove the sprintf dependency and integrate it into 8chan's Options as opposed to Pashe's. + * I also changed initHover() to also bind on new_post. + * Thanks Pashe for using WTFPL. + */ + +if (active_page === "catalog" || active_page === "thread" || active_page === "index") { +$(document).on('ready', function(){ + +if (window.Options && Options.get_tab('general')) { + Options.extend_tab("general", + "
    Image hover" + + ("") + + ("") + + ("") + + "
    "); +} + +$('.image-hover').on('change', function(){ + var setting = $(this).attr('id'); + + localStorage[setting] = $(this).children('input').is(':checked'); +}); + +if (!localStorage.imageHover || !localStorage.catalogImageHover || !localStorage.imageHoverFollowCursor) { + localStorage.imageHover = 'false'; + localStorage.catalogImageHover = 'false'; + localStorage.imageHoverFollowCursor = 'false'; +} + +if (getSetting('imageHover')) $('#imageHover>input').prop('checked', 'checked'); +if (getSetting('catalogImageHover')) $('#catalogImageHover>input').prop('checked', 'checked'); +if (getSetting('imageHoverFollowCursor')) $('#imageHoverFollowCursor>input').prop('checked', 'checked'); + +function getFileExtension(filename) { //Pashe, WTFPL + if (filename.match(/\.([a-z0-9]+)(&loop.*)?$/i) !== null) { + return filename.match(/\.([a-z0-9]+)(&loop.*)?$/i)[1]; + } else if (filename.match(/https?:\/\/(www\.)?youtube.com/)) { + return 'Youtube'; + } else { + return "unknown: " + filename; + } +} + +function isImage(fileExtension) { //Pashe, WTFPL + return ($.inArray(fileExtension, ["jpg", "jpeg", "gif", "png"]) !== -1); +} + +function isVideo(fileExtension) { //Pashe, WTFPL + return ($.inArray(fileExtension, ["webm", "mp4"]) !== -1); +} + +function isOnCatalog() { + return window.active_page === "catalog"; +} + +function isOnThread() { + return window.active_page === "thread"; +} + +function getSetting(key) { + return (localStorage[key] == 'true'); +} + +function initImageHover() { //Pashe, influenced by tux, et al, WTFPL + if (!getSetting("imageHover") && !getSetting("catalogImageHover")) {return;} + + var selectors = []; + + if (getSetting("imageHover")) {selectors.push("img.post-image", "canvas.post-image");} + if (getSetting("catalogImageHover") && isOnCatalog()) { + selectors.push(".thread-image"); + $(".theme-catalog div.thread").css("position", "inherit"); + } + + function bindEvents(el) { + $(el).find(selectors.join(", ")).each(function () { + if ($(this).parent().data("expanded")) {return;} + + var $this = $(this); + + $this.on("mousemove", imageHoverStart); + $this.on("mouseout", imageHoverEnd); + $this.on("click", imageHoverEnd); + }); + } + + bindEvents(document.body); + $(document).on('new_post', function(e, post) { + bindEvents(post); + }); +} + +function imageHoverStart(e) { //Pashe, anonish, WTFPL + var hoverImage = $("#chx_hoverImage"); + + if (hoverImage.length) { + if (getSetting("imageHoverFollowCursor")) { + var scrollTop = $(window).scrollTop(); + var imgY = e.pageY; + var imgTop = imgY; + var windowWidth = $(window).width(); + var imgWidth = hoverImage.width() + e.pageX; + + if (imgY < scrollTop + 15) { + imgTop = scrollTop; + } else if (imgY > scrollTop + $(window).height() - hoverImage.height() - 15) { + imgTop = scrollTop + $(window).height() - hoverImage.height() - 15; + } + + if (imgWidth > windowWidth) { + hoverImage.css({ + 'left': (e.pageX + (windowWidth - imgWidth)), + 'top' : imgTop, + }); + } else { + hoverImage.css({ + 'left': e.pageX, + 'top' : imgTop, + }); + } + + hoverImage.appendTo($("body")); + } + + return; + } + + var $this = $(this); + + var fullUrl; + if ($this.parent().attr("href").match("src")) { + fullUrl = $this.parent().attr("href"); + } else if (isOnCatalog()) { + $this.css("cursor", "progress"); + fullUrl = $this.attr("src"); + $.ajax(($this.parent().attr("href").replace(/\.html$/, ".json")), { + success: function (result) { + $this.css("cursor", "unset"); + fullUrl = result.posts[0].tim + result.posts[0].ext; + if (!isImage(getFileExtension(fullUrl))) {return;} + $("#chx_hoverImage").attr("src", "/" + thisBoard + "/" + fullUrl); + $("#chx_hoverImage").on("load", function() {$this.css("cursor", "none")}); + }, + async: true, + cache: true + }); + } + + if (isVideo(getFileExtension(fullUrl))) {return;} + + hoverImage = $(''); + if (getSetting("imageHoverFollowCursor")) { + hoverImage.css({ + "position" : "absolute", + "z-index" : 101, + "pointer-events": "none", + "max-width" : $(window).width(), + "max-height" : $(window).height(), + 'left' : e.pageX, + 'top' : imgTop, + }); + } else { + hoverImage.css({ + "position" : "fixed", + "top" : 0, + "right" : 0, + "z-index" : 101, + "pointer-events": "none", + "max-width" : "100%", + "max-height" : "100%", + }); + } + hoverImage.appendTo($("body")); + if (isOnThread()) {$this.css("cursor", "none");} +} + +function imageHoverEnd() { //Pashe, WTFPL + $("#chx_hoverImage").remove(); +} + +initImageHover(); +}); +} + diff --git a/js/inline-expanding.js b/js/inline-expanding.js index 92069689..e316fbb7 100644 --- a/js/inline-expanding.js +++ b/js/inline-expanding.js @@ -48,8 +48,8 @@ $(document).ready(function(){ if (i > -1) { waiting.splice(i, 1); } - if (ele.dataset.imageLoading === 'true') { - ele.dataset.imageLoading = 'false'; + if ($(ele).data('imageLoading') === 'true') { + $(ele).data('imageLoading', 'false'); clearTimeout(ele.timeout); --loading; } @@ -73,7 +73,7 @@ $(document).ready(function(){ $.when($loadstart).done(function () { // once fully loaded, update the waiting queue --loading; - ele.dataset.imageLoading = 'false'; + $(ele).data('imageLoading', 'false'); update(); }); }); @@ -83,7 +83,7 @@ $(document).ready(function(){ }); img.setAttribute('src', ele.href); - ele.dataset.imageLoading = 'true'; + $(ele).data('imageLoading', 'true'); ele.timeout = onLoadStart(img); }); @@ -112,9 +112,9 @@ $(document).ready(function(){ return false; if (e.which == 2 || e.ctrlKey) // open in new tab return true; - if (!this.dataset.expanded) { + if (!$(this).data('expanded')) { this.parentNode.removeAttribute('style'); - this.dataset.expanded = 'true'; + $(this).data('expanded', 'true'); if (thumb.tagName === 'CANVAS') { canvas = thumb; @@ -144,12 +144,12 @@ $(document).ready(function(){ } if (~this.parentNode.className.indexOf('multifile')) - this.parentNode.style.width = (parseInt(this.dataset.width)+40)+'px'; + this.parentNode.style.width = (parseInt($(this).data('width'))+40)+'px'; thumb.style.opacity = ''; thumb.style.display = ''; if (thumb.nextSibling) this.removeChild(thumb.nextSibling); //full image loaded or loading - delete this.dataset.expanded; + $(this).removeData('expanded'); delete thumb.style.filter; // do the scrolling after page reflow diff --git a/js/post-filter.js b/js/post-filter.js new file mode 100644 index 00000000..c2c5a764 --- /dev/null +++ b/js/post-filter.js @@ -0,0 +1,799 @@ +if (active_page === 'thread' || active_page === 'index') { + $(document).ready(function () { + 'use strict'; + // returns blacklist object from storage + function getList() { + return JSON.parse(localStorage.postFilter); + } + + // stores blacklist into storage and reruns the filter + function setList(blacklist) { + localStorage.postFilter = JSON.stringify(blacklist); + $(document).trigger('filter_page'); + } + + // unit: seconds + function timestamp() { + return Math.floor((new Date()).getTime() / 1000); + } + + function initList(list, boardId, threadId) { + if (typeof list.postFilter[boardId] == 'undefined') + list.postFilter[boardId] = {}; + list.nextPurge[boardId] = {}; + if (typeof list.postFilter[boardId][threadId] == 'undefined') { + list.postFilter[boardId][threadId] = []; + } + list.nextPurge[boardId][threadId] = {timestamp: timestamp(), interval: 86400}; // 86400 seconds == 1 day + } + + var blacklist = { + add: { + name: function (posterName) { + var list = getList(); + var filter = list.nameFilter; + + for (var i in filter) { + if (filter[i].name == posterName) return; + } + filter.push({ + name: posterName + }); + setList(list); + drawFilterList(); + }, + trip: function (posterTrip) { + var list = getList(); + var filter = list.nameFilter; + + for (var i in filter) { + if (filter[i].trip == posterTrip) return; + } + filter.push({ + trip: posterTrip + }); + setList(list); + drawFilterList(); + }, + post: function (boardId, threadId, postId, hideReplies) { + var list = getList(); + var filter = list.postFilter; + + initList(list, boardId, threadId); + + for (var i in filter[boardId][threadId]) { + if (filter[boardId][threadId][i].post == postId) return; + } + filter[boardId][threadId].push({ + post: postId, + hideReplies: hideReplies + }); + setList(list); + }, + uid: function (boardId, threadId, uniqueId, hideReplies) { + var list = getList(); + var filter = list.postFilter; + + initList(list, boardId, threadId); + + for (var i in filter[boardId][threadId]) { + if (filter[boardId][threadId][i].uid == uniqueId) return; + } + filter[boardId][threadId].push({ + uid: uniqueId, + hideReplies: hideReplies + }); + setList(list); + } + }, + remove: { + name: function (posterName) { + var list = getList(); + var filter = list.nameFilter; + + for (var i=0; i
    ').append( + $('