diff --git a/inc/8chan-mod-pages.php b/inc/8chan-mod-pages.php index 1474cf20..3375820a 100644 --- a/inc/8chan-mod-pages.php +++ b/inc/8chan-mod-pages.php @@ -317,6 +317,9 @@ FLAGS; $code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "
\$1
");' : ''; $katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "\$1"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : ''; $user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : ''; + $captcha = isset($_POST['captcha']) ? 'true' : 'false'; + $captcha_charset = base64_encode(isset($_POST['captcha_charset']) && $_POST['captcha_charset'] ? $_POST['captcha_charset'] : 'abcdefghijklmnopqrstuvwxyz'); + $oekaki_js = <<fetch(PDO::FETCH_ASSOC)) { diff --git a/inc/config.php b/inc/config.php index 10c9faa5..3adabcc9 100644 --- a/inc/config.php +++ b/inc/config.php @@ -272,6 +272,8 @@ 'embed', 'recaptcha_challenge_field', 'recaptcha_response_field', + 'captcha_cookie', + 'captcha_text', 'spoiler', 'page', 'file_url', @@ -300,6 +302,19 @@ $config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f'; $config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_'; + $config['captcha'] = array(); + + // Enable custom captcha provider + $config['captcha']['enabled'] = false; + + // Custom captcha provider path + $config['captcha']['provider_get'] = 'http://8chan.vichan.net/captcha/entrypoint.php'; + $config['captcha']['provider_check'] = 'http://8chan.vichan.net/captcha/entrypoint.php'; + + // Custom captcha extra field (eg. charset) + $config['captcha']['extra'] = 'abcdefghijklmnopqrstuvwxyz'; + + /* * Custom filters detect certain posts and reject/ban accordingly. They are made up of a condition and an * action (for when ALL conditions are met). As every single post has to be put through each filter, @@ -602,6 +617,17 @@ // How many ban appeals can be made for a single ban? $config['ban_appeals_max'] = 1; + + // Blacklisted board names. Default values to protect existing folders in the core codebase. + $config['banned_boards'] = array( + '.git', + 'inc', + 'js', + 'static', + 'stylesheets', + 'templates', + 'tools' + ); // Show moderator name on ban page. $config['show_modname'] = false; @@ -1326,8 +1352,8 @@ // Capcode permissions. $config['mod']['capcode'] = array( // JANITOR => array('Janitor'), - MOD => array('Mod'), - ADMIN => true + MOD => array('Mod'), + ADMIN => true ); // Example: Allow mods to post with "## Moderator" as well @@ -1410,7 +1436,7 @@ $config['mod']['view_banlist'] = MOD; // View the username of the mod who made a ban $config['mod']['view_banstaff'] = MOD; - // If the moderator doesn't fit the $config['mod']['view_banstaff''] (previous) permission, show him just + // If the moderator doesn't fit the $config['mod']['view_banstaff'] (previous) permission, show him just // a "?" instead. Otherwise, it will be "Mod" or "Admin". $config['mod']['view_banquestionmark'] = false; // Show expired bans in the ban list (they are kept in cache until the culprit returns) diff --git a/inc/instance-config.php b/inc/instance-config.php index aa45f960..da57903c 100644 --- a/inc/instance-config.php +++ b/inc/instance-config.php @@ -91,6 +91,7 @@ $config['additional_javascript'][] = 'js/jquery.min.js'; $config['additional_javascript'][] = 'js/jquery.mixitup.min.js'; $config['additional_javascript'][] = 'js/catalog.js'; + $config['additional_javascript'][] = 'js/captcha.js'; $config['additional_javascript'][] = 'js/jquery.tablesorter.min.js'; $config['additional_javascript'][] = 'js/options.js'; $config['additional_javascript'][] = 'js/style-select.js'; diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 1a56c99c..61920b46 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -495,7 +495,15 @@ function mod_new_board() { if (openBoard($_POST['uri'])) { error(sprintf($config['error']['boardexists'], $board['url'])); } - + foreach ($config['banned_boards'] as $i => $w) { + if ($w[0] !== '/') { + if (strpos($_POST['uri'],$w) !== false) + error(_("Cannot create board with banned word $w")); + } else { + if (preg_match($w,$_POST['uri'])) + error(_("Cannot create board matching banned pattern $w")); + } + } $query = prepare('INSERT INTO ``boards`` (``uri``, ``title``, ``subtitle``) VALUES (:uri, :title, :subtitle)'); $query->bindValue(':uri', $_POST['uri']); $query->bindValue(':title', $_POST['title']); diff --git a/install.sql b/install.sql index 9af84787..9e15396a 100644 --- a/install.sql +++ b/install.sql @@ -220,6 +220,7 @@ CREATE TABLE IF NOT EXISTS `reports` ( `board` varchar(58) CHARACTER SET utf8 DEFAULT NULL, `post` int(11) NOT NULL, `reason` text NOT NULL, + `local` tinyint(1) NOT NULL DEFAULT '0', `global` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; diff --git a/js/captcha.js b/js/captcha.js new file mode 100644 index 00000000..1824214b --- /dev/null +++ b/js/captcha.js @@ -0,0 +1,44 @@ +var tout; + +function redo_events(provider, extra) { + $('.captcha .captcha_text, textarea[id="body"]').off("focus").one("focus", function() { actually_load_captcha(provider, extra); }); +} + +function actually_load_captcha(provider, extra) { + $('.captcha .captcha_text, textarea[id="body"]').off("focus"); + + if (tout !== undefined) { + clearTimeout(tout); + } + + $.getJSON(provider, {mode: 'get', extra: extra}, function(json) { + $(".captcha .captcha_cookie").val(json.cookie); + $(".captcha .captcha_html").html(json.captchahtml); + + setTimeout(function() { + redo_events(provider, extra); + }, json.expires_in * 1000); + }); +} + +function load_captcha(provider, extra) { + $(function() { + $(".captcha>td").html(""+ + ""+ + "
"); + + $("#quick-reply .captcha .captcha_text").prop("placeholder", _("Verification")); + + $(".captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); }); + $(document).on("ajax_after_post", function() { actually_load_captcha(provider, extra); }); + redo_events(provider, extra); + + $(window).on("quick-reply", function() { + redo_events(provider, extra); + $("#quick-reply .captcha .captcha_html").html($("form:not(#quick-reply) .captcha .captcha_html").html()); + $("#quick-reply .captcha .captcha_cookie").val($("form:not(#quick-reply) .captcha .captcha_cookie").html()); + $("#quick-reply .captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); }); + }); + }); +} + diff --git a/js/jquery.tablesorter.min.js b/js/jquery.tablesorter.min.js new file mode 100644 index 00000000..b8605df1 --- /dev/null +++ b/js/jquery.tablesorter.min.js @@ -0,0 +1,4 @@ + +(function($){$.extend({tablesorter:new +function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((ab)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i $th.contents().filter(function() { return this.nodeType == 3; // Node.TEXT_NODE }).remove(); $th.contents().appendTo($dummyStuff); $th.remove(); - + if ($td.find('input[name="password"]').length) { // Hide password field $(this).hide(); @@ -280,7 +280,7 @@ $postForm.find('textarea[name="body"]').removeAttr('id').removeAttr('cols').attr('placeholder', _('Comment')); - $postForm.find('textarea:not([name="body"]),input[type="hidden"]').removeAttr('id').appendTo($dummyStuff); + $postForm.find('textarea:not([name="body"]),input[type="hidden"]:not(.captcha_cookie)').removeAttr('id').appendTo($dummyStuff); $postForm.find('br').remove(); $postForm.find('table').prepend('\ diff --git a/js/thread-stats.js b/js/thread-stats.js index 4050fb67..7c13d66c 100644 --- a/js/thread-stats.js +++ b/js/thread-stats.js @@ -79,6 +79,7 @@ $(document).ready(function(){ } $('#thread_stats_page').text(page); if (!found) $('#thread_stats_page').css('color','red'); + else $('#thread_stats_page').css('color',''); }); } // load the current page the thread is on. @@ -99,6 +100,7 @@ $(document).ready(function(){ } $('#thread_stats_page').text(page); if (!found) $('#thread_stats_page').css('color','red'); + else $('#thread_stats_page').css('color',''); }); },30000); $('body').append(''); diff --git a/post.php b/post.php index 650b431b..bffc91c0 100644 --- a/post.php +++ b/post.php @@ -232,6 +232,21 @@ elseif (isset($_POST['post'])) { } } + // Same, but now with our custom captcha provider + if ($config['captcha']['enabled']) { + $resp = file_get_contents($config['captcha']['provider_check'] . "?" . http_build_query([ + 'mode' => 'check', + 'text' => $_POST['captcha_text'], + 'extra' => $config['captcha']['extra'], + 'cookie' => $_POST['captcha_cookie'] + ])); + + if ($resp !== '1') { + error($config['error']['captcha'] . + ''); + } + } + //if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) || //(!$post['op'] && $_POST['post'] == $config['button_reply']))) //error($config['error']['bot']); diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html index fe6c6a3e..5481f49b 100644 --- a/templates/mod/dashboard.html +++ b/templates/mod/dashboard.html @@ -127,6 +127,7 @@ {% endif %} {% if mod|hasPermission(config.mod.manageboards) %} [{% trans 'edit' %}] + [{% trans 'settings' %}] {% endif %} {% endif %} diff --git a/templates/mod/settings.html b/templates/mod/settings.html index 487f82d8..74319aa4 100644 --- a/templates/mod/settings.html +++ b/templates/mod/settings.html @@ -44,6 +44,8 @@ {% trans %}Enable dice rolling{% endtrans %} {% trans %}Don't allow users to repost images{% endtrans %} {% trans %}Allow a poster to delete his own posts{% endtrans %} + {% trans %}Enable captcha{% endtrans %} + {% trans %}Captcha charset{% endtrans %} {% trans %}Language{% endtrans %}
{% trans %}To contribute translations, register at Transifex{% endtrans %}