forked from leftypol/leftypol
Merged lainchan with vichan master at 25/9/2016 0bd63149b7
@czaks czaks committed on GitHub 2 days ago
This commit is contained in:
commit
c58e37ce39
114 changed files with 5580 additions and 898 deletions
|
@ -32,6 +32,7 @@ class Api {
|
|||
'images' => 'images',
|
||||
'sticky' => 'sticky',
|
||||
'locked' => 'locked',
|
||||
'cycle' => 'cyclical',
|
||||
'bump' => 'last_modified',
|
||||
'embed' => 'embed',
|
||||
);
|
||||
|
@ -92,7 +93,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) && $file->hash) {
|
||||
$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) {
|
||||
|
|
|
@ -174,7 +174,7 @@ class Bans {
|
|||
|
||||
if ($ban['post'] && !$hide_message) {
|
||||
$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']);
|
||||
|
||||
|
|
202
inc/config.php
202
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
|
||||
|
@ -511,6 +518,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.
|
||||
|
||||
|
@ -550,6 +564,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;
|
||||
|
||||
|
@ -763,7 +780,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.
|
||||
|
@ -804,8 +821,17 @@
|
|||
// 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;
|
||||
|
||||
// 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;
|
||||
|
@ -928,8 +954,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(
|
||||
|
@ -998,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
|
||||
|
@ -1193,16 +1223,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();
|
||||
|
||||
/*
|
||||
* ====================
|
||||
|
@ -1235,6 +1323,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'] = ' <span class="capcode">## %s</span>';
|
||||
|
@ -1378,6 +1468,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
|
||||
|
@ -1488,6 +1581,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();
|
||||
|
@ -1534,25 +1630,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;
|
||||
|
||||
/*
|
||||
* ====================
|
||||
|
@ -1588,6 +1689,45 @@
|
|||
// Example: Adding the pre-markup post body to the API as "com_nomarkup".
|
||||
// $config['api']['extra_fields'] = array('body_nomarkup' => 'com_nomarkup');
|
||||
|
||||
/*
|
||||
* ==================
|
||||
* 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();
|
||||
|
||||
// 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. 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
|
||||
|
@ -1670,8 +1810,28 @@
|
|||
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$2/0.jpg" class="post-image"/>'.
|
||||
'</a></div>';
|
||||
|
||||
// Slack Report Notification
|
||||
$config['slack'] = true;
|
||||
$config['slack_channel'] = "reports";
|
||||
$config['slack_incoming_webhook_endpoint'] = "https://hooks.slack.com/services/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";
|
||||
// Slack Report Notification
|
||||
$config['slack'] = true;
|
||||
$config['slack_channel'] = "reports";
|
||||
$config['slack_incoming_webhook_endpoint'] = "https://hooks.slack.com/services/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";
|
||||
|
||||
// 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;
|
||||
|
||||
// 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';
|
||||
|
|
108
inc/controller.php
Normal file
108
inc/controller.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
// This file contains the controller part of vichan
|
||||
|
||||
// don't bother with that unless you use smart build or advanced build
|
||||
// you can use those parts for your own implementations though :^)
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
function sb_board($b, $page = 1) { global $config, $build_pages; $page = (int)$page;
|
||||
if ($page < 1) return false;
|
||||
if (!openBoard($b)) return false;
|
||||
if ($page > $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;
|
||||
}
|
||||
|
|
@ -71,6 +71,64 @@ function createBoardlist($mod=false) {
|
|||
);
|
||||
}
|
||||
|
||||
function error($message, $priority = true, $debug_stuff = false) {
|
||||
global $board, $mod, $config, $db_error;
|
||||
|
||||
if ($config['syslog'] && $priority !== false) {
|
||||
// Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant.
|
||||
_syslog($priority !== true ? $priority : LOG_NOTICE, $message);
|
||||
}
|
||||
|
||||
if (defined('STDIN')) {
|
||||
// Running from CLI
|
||||
echo('Error: ' . $message . "\n");
|
||||
debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
die();
|
||||
}
|
||||
|
||||
if ($config['debug'] && isset($db_error)) {
|
||||
$debug_stuff = array_combine(array('SQLSTATE', 'Error code', 'Error message'), $db_error);
|
||||
}
|
||||
|
||||
if ($config['debug']) {
|
||||
$debug_stuff['backtrace'] = debug_backtrace();
|
||||
}
|
||||
|
||||
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)) {
|
||||
$item = array_filter($item, $debug_callback);
|
||||
}
|
||||
return ($item !== $pw || !$pw);
|
||||
};
|
||||
|
||||
|
||||
if ($debug_stuff)
|
||||
$debug_stuff = array_filter($debug_stuff, $debug_callback);
|
||||
|
||||
die(Element('page.html', array(
|
||||
'config' => $config,
|
||||
'title' => _('Error'),
|
||||
'subtitle' => _('An error has occured.'),
|
||||
'body' => Element('error.html', array(
|
||||
'config' => $config,
|
||||
'message' => $message,
|
||||
'mod' => $mod,
|
||||
'board' => isset($board) ? $board : false,
|
||||
'debug' => is_array($debug_stuff) ? str_replace("\n", ' ', utf8tohtml(print_r($debug_stuff, true))) : utf8tohtml($debug_stuff)
|
||||
))
|
||||
)));
|
||||
}
|
||||
|
||||
function loginForm($error=false, $username=false, $redirect=false) {
|
||||
global $config;
|
||||
|
|
|
@ -19,6 +19,12 @@ 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/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
|
||||
|
||||
if (!extension_loaded('gettext')) {
|
||||
require_once 'inc/lib/gettext/gettext.inc';
|
||||
}
|
||||
|
@ -86,6 +92,8 @@ function loadConfig() {
|
|||
'db',
|
||||
'api',
|
||||
'cache',
|
||||
'lock',
|
||||
'queue',
|
||||
'cookies',
|
||||
'error',
|
||||
'dir',
|
||||
|
@ -123,7 +131,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';
|
||||
|
@ -134,13 +142,13 @@ 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];
|
||||
}
|
||||
|
||||
file_put_contents($fn, $config['locale']);
|
||||
@file_put_contents($fn, $config['locale']);
|
||||
}
|
||||
|
||||
if ($config['locale'] != $current_locale) {
|
||||
|
@ -480,7 +488,8 @@ function setupBoard($array) {
|
|||
$board = array(
|
||||
'uri' => $array['uri'],
|
||||
'title' => $array['title'],
|
||||
'subtitle' => $array['subtitle']
|
||||
'subtitle' => $array['subtitle'],
|
||||
#'indexed' => $array['indexed'],
|
||||
);
|
||||
|
||||
// older versions
|
||||
|
@ -505,14 +514,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();
|
||||
|
@ -836,7 +850,7 @@ function displayBan($ban) {
|
|||
Element('page.html', array(
|
||||
'title' => _('Banned!'),
|
||||
'config' => $config,
|
||||
'nojavascript' => true,
|
||||
'boardlist' => createBoardlist(isset($mod) ? $mod : false),
|
||||
'body' => Element('banned.html', array(
|
||||
'config' => $config,
|
||||
'ban' => $ban,
|
||||
|
@ -974,7 +988,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'])) {
|
||||
|
@ -1014,6 +1028,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 {
|
||||
|
@ -1086,6 +1106,8 @@ function deleteFile($id, $remove_entirely_if_already=true, $file=null) {
|
|||
$files = json_decode($post['files']);
|
||||
$file_to_delete = $file !== false ? $files[(int)$file] : (object)array('file' => false);
|
||||
|
||||
if (!$files[0]) error(_('That post has no files.'));
|
||||
|
||||
if ($files[0]->file == 'deleted' && $post['num_files'] == 1 && !$post['thread'])
|
||||
return; // Can't delete OP's image completely.
|
||||
|
||||
|
@ -1097,8 +1119,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);
|
||||
|
@ -1120,19 +1144,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));
|
||||
|
||||
|
@ -1221,7 +1248,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']);
|
||||
|
||||
|
@ -1232,6 +1259,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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1245,7 +1288,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 = '';
|
||||
|
@ -1276,6 +1320,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']);
|
||||
|
@ -1315,7 +1360,10 @@ function index($page, $mod=false) {
|
|||
}
|
||||
|
||||
$threads[] = $thread;
|
||||
$body .= $thread->build(true);
|
||||
|
||||
if (!$brief) {
|
||||
$body .= $thread->build(true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($config['file_board']) {
|
||||
|
@ -1536,27 +1584,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;
|
||||
|
||||
|
@ -1567,17 +1616,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) {
|
||||
$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']);
|
||||
|
@ -1585,13 +1638,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);
|
||||
|
@ -1605,13 +1659,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);
|
||||
|
@ -1672,13 +1726,15 @@ function buildJavascript() {
|
|||
function checkDNSBL() {
|
||||
global $config;
|
||||
|
||||
|
||||
if (isIPv6())
|
||||
return; // No IPv6 support yet.
|
||||
|
||||
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;
|
||||
|
||||
|
@ -1811,7 +1867,11 @@ function extract_modifiers($body) {
|
|||
return $modifiers;
|
||||
}
|
||||
|
||||
function markup(&$body, $track_cites = false) {
|
||||
function remove_modifiers($body) {
|
||||
return preg_replace('@<tinyboard ([\w\s]+)>(.+?)</tinyboard>@usm', '', $body);
|
||||
}
|
||||
|
||||
function markup(&$body, $track_cites = false, $op = false) {
|
||||
global $board, $config, $markup_urls;
|
||||
|
||||
$modifiers = extract_modifiers($body);
|
||||
|
@ -1907,7 +1967,7 @@ function markup(&$body, $track_cites = false) {
|
|||
}
|
||||
|
||||
if (isset($cited_posts[$cite])) {
|
||||
$replacement = '<a onclick="highlightReply(\''.$cite.'\');" href="' .
|
||||
$replacement = '<a onclick="highlightReply(\''.$cite.'\', event);" href="' .
|
||||
$config['root'] . $board['dir'] . $config['dir']['res'] .
|
||||
link_for(array('id' => $cite, 'thread' => $cited_posts[$cite])) . '#' . $cite . '">' .
|
||||
'>>' . $cite .
|
||||
|
@ -2006,7 +2066,7 @@ function markup(&$body, $track_cites = false) {
|
|||
|
||||
$replacement = '<a ' .
|
||||
($_board == $board['uri'] ?
|
||||
'onclick="highlightReply(\''.$cite.'\');" '
|
||||
'onclick="highlightReply(\''.$cite.'\', event);" '
|
||||
: '') . 'href="' . $link . '">' .
|
||||
'>>>/' . $_board . '/' . $cite .
|
||||
'</a>';
|
||||
|
@ -2112,16 +2172,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;
|
||||
|
@ -2145,7 +2196,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));
|
||||
|
@ -2180,26 +2233,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);
|
||||
|
@ -2340,7 +2393,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}"];
|
||||
|
@ -2429,7 +2482,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
|
||||
|
@ -2565,7 +2618,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");
|
||||
|
@ -2640,3 +2693,103 @@ 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);
|
||||
}
|
||||
|
||||
function generation_strategy($fun, $array=array()) { global $config;
|
||||
$action = false;
|
||||
|
||||
foreach ($config['generation_strategies'] as $s) {
|
||||
if ($action = $s($fun, $array)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($action[0]) {
|
||||
case 'immediate':
|
||||
return 'rebuild';
|
||||
case 'defer':
|
||||
// Ok, it gets interesting here :)
|
||||
get_queue('generate')->push(serialize(array('build', $fun, $array, $action)));
|
||||
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;
|
||||
if (php_sapi_name() == 'cli') return false;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
|
|
39
inc/lock.php
Normal file
39
inc/lock.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
class Lock {
|
||||
function __construct($key) { global $config;
|
||||
if ($config['lock']['enabled'] == 'fs') {
|
||||
$key = str_replace('/', '::', $key);
|
||||
$key = str_replace("\0", '', $key);
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
143
inc/mod/auth.php
143
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 = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE BINARY `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['version'], $password);
|
||||
|
||||
if ($ok) {
|
||||
if ($config['password_crypt_version'] > $version) {
|
||||
// It's time to upgrade the password hashing method!
|
||||
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(':version', $user['version']);
|
||||
$query->bindValue(':id', $user['id']);
|
||||
$query->execute() or error(db_error($query));
|
||||
}
|
||||
|
||||
return $mod = array(
|
||||
'id' => $user['id'],
|
||||
'type' => $user['type'],
|
||||
|
@ -81,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);
|
||||
|
@ -97,39 +146,6 @@ function modLog($action, $_board=null) {
|
|||
_syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action);
|
||||
}
|
||||
|
||||
// Validate session
|
||||
|
||||
if (isset($_COOKIE[$config['cookies']['mod']])) {
|
||||
// Should be username:hash:salt
|
||||
$cookie = explode(':', $_COOKIE[$config['cookies']['mod']]);
|
||||
if (count($cookie) != 3) {
|
||||
// Malformed cookies
|
||||
destroyCookies();
|
||||
mod_login();
|
||||
exit;
|
||||
}
|
||||
|
||||
$query = prepare("SELECT `id`, `type`, `boards`, `password` FROM ``mods`` WHERE `username` = :username");
|
||||
$query->bindValue(':username', $cookie[0]);
|
||||
$query->execute() or error(db_error($query));
|
||||
$user = $query->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// validate password hash
|
||||
if ($cookie[1] !== mkhash($cookie[0], $user['password'], $cookie[2])) {
|
||||
// Malformed cookies
|
||||
destroyCookies();
|
||||
mod_login();
|
||||
exit;
|
||||
}
|
||||
|
||||
$mod = array(
|
||||
'id' => $user['id'],
|
||||
'type' => $user['type'],
|
||||
'username' => $cookie[0],
|
||||
'boards' => explode(',', $user['boards'])
|
||||
);
|
||||
}
|
||||
|
||||
function create_pm_header() {
|
||||
global $mod, $config;
|
||||
|
||||
|
@ -163,4 +179,37 @@ function make_secure_link_token($uri) {
|
|||
return substr(sha1($config['cookies']['salt'] . '-' . $uri . '-' . $mod['id']), 0, 8);
|
||||
}
|
||||
|
||||
|
||||
function check_login($prompt = false) {
|
||||
global $config, $mod;
|
||||
// Validate session
|
||||
if (isset($_COOKIE[$config['cookies']['mod']])) {
|
||||
// Should be username:hash:salt
|
||||
$cookie = explode(':', $_COOKIE[$config['cookies']['mod']]);
|
||||
if (count($cookie) != 3) {
|
||||
// Malformed cookies
|
||||
destroyCookies();
|
||||
if ($prompt) mod_login();
|
||||
exit;
|
||||
}
|
||||
|
||||
$query = prepare("SELECT `id`, `type`, `boards`, `password` FROM ``mods`` WHERE `username` = :username");
|
||||
$query->bindValue(':username', $cookie[0]);
|
||||
$query->execute() or error(db_error($query));
|
||||
$user = $query->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// validate password hash
|
||||
if ($cookie[1] !== mkhash($cookie[0], $user['password'], $cookie[2])) {
|
||||
// Malformed cookies
|
||||
destroyCookies();
|
||||
if ($prompt) mod_login();
|
||||
exit;
|
||||
}
|
||||
|
||||
$mod = array(
|
||||
'id' => $user['id'],
|
||||
'type' => $user['type'],
|
||||
'username' => $cookie[0],
|
||||
'boards' => explode(',', $user['boards'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ function mod_page($title, $template, $args, $subtitle = false) {
|
|||
'hide_dashboard_link' => $template == 'mod/dashboard.html',
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'nojavascript' => true,
|
||||
'boardlist' => createBoardlist($mod),
|
||||
'body' => Element($template,
|
||||
array_merge(
|
||||
array('config' => $config, 'mod' => $mod),
|
||||
|
@ -612,7 +612,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");
|
||||
|
@ -625,14 +625,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) {
|
||||
|
@ -647,7 +647,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) {
|
||||
|
@ -702,6 +702,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('/(?:<a href="\?\/IP\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}">)?(\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;
|
||||
|
||||
|
@ -850,7 +886,7 @@ function mod_page_ip($ip) {
|
|||
|
||||
$args['security_token'] = make_secure_link_token('IP/' . $ip);
|
||||
|
||||
mod_page(sprintf('%s: %s', _('IP'), $ip), 'mod/view_ip.html', $args, $args['hostname']);
|
||||
mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']);
|
||||
}
|
||||
|
||||
function mod_ban() {
|
||||
|
@ -1057,6 +1093,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;
|
||||
|
||||
|
@ -1488,6 +1546,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'] .= "<tinyboard $key>$value</tinyboard>";
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -1516,15 +1583,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));
|
||||
}
|
||||
}
|
||||
|
@ -1668,6 +1740,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;
|
||||
|
@ -1753,13 +1827,12 @@ function mod_user($uid) {
|
|||
}
|
||||
|
||||
if ($_POST['password'] != '') {
|
||||
$salt = generate_salt();
|
||||
$password = hash('sha256', $salt . sha1($_POST['password']));
|
||||
|
||||
$query = prepare('UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id');
|
||||
list($version, $password) = crypt_password($_POST['password']);
|
||||
|
||||
$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']) . ' <small>(#' . $user['id'] . ')</small>');
|
||||
|
@ -1780,13 +1853,12 @@ 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($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');
|
||||
|
@ -1853,13 +1925,12 @@ 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($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));
|
||||
|
@ -2600,6 +2671,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 = "<div class='ban'>$write</div>";
|
||||
$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;
|
||||
|
||||
|
@ -2716,3 +2948,4 @@ function mod_debug_apc() {
|
|||
|
||||
mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars));
|
||||
}
|
||||
|
||||
|
|
152
inc/nntpchan/nntpchan.php
Normal file
152
inc/nntpchan/nntpchan.php
Normal file
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
/*
|
||||
* Copyright (c) 2016 vichan-devel
|
||||
*/
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
function gen_msgid($board, $id) {
|
||||
global $config;
|
||||
|
||||
$b = preg_replace("/[^0-9a-zA-Z$]/", 'x', $board);
|
||||
$salt = sha1($board . "|" . $id . "|" . $config['nntpchan']['salt']);
|
||||
$salt = substr($salt, 0, 7);
|
||||
$salt = base_convert($salt, 16, 36);
|
||||
|
||||
return "<$b.$id.$salt@".$config['nntpchan']['domain'].">";
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
global $config;
|
||||
$server = $config["nntpchan"]["server"];
|
||||
$s = fsockopen("tcp://$server");
|
||||
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);
|
||||
fputs($s, "QUIT\r\n");
|
||||
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'] . " <poster@" . $config['nntpchan']['domain'] . ">";
|
||||
|
||||
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, 18);
|
||||
}
|
||||
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);
|
||||
}
|
30
inc/nntpchan/tests.php
Normal file
30
inc/nntpchan/tests.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
define('TINYBOARD', 'fuck yeah');
|
||||
require_once('nntpchan.php');
|
||||
|
||||
|
||||
die();
|
||||
|
||||
$time = time();
|
||||
echo "\n@@@@ Thread:\n";
|
||||
echo $m0 = gennntp(["From" => "czaks <marcin@6irc.net>", "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 <marcin@6irc.net>", "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 <marcin@6irc.net>", "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 <marcin@6irc.net>", "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>");
|
||||
|
28
inc/polyfill.php
Normal file
28
inc/polyfill.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
// PHP 5.4
|
||||
|
||||
if (!function_exists('hex2bin')) {
|
||||
function hex2bin($data) {
|
||||
return pack("H*" , $hex_string);
|
||||
}
|
||||
}
|
||||
|
||||
// PHP 5.6
|
||||
|
||||
if (!function_exists('hash_equals')) {
|
||||
function hash_equals($ours, $theirs) {
|
||||
$ours = (string)$ours;
|
||||
$theirs = (string)$theirs;
|
||||
|
||||
$tlen = strlen($theirs);
|
||||
$olen = strlen($ours);
|
||||
|
||||
$answer = 0;
|
||||
for ($i = 0; $i < $tlen; $i++) {
|
||||
$answer |= ord($ours[$olen > $i ? $i : 0]) ^ ord($theirs[$i]);
|
||||
}
|
||||
|
||||
return $answer === 0 && $olen === $tlen;
|
||||
}
|
||||
}
|
49
inc/queue.php
Normal file
49
inc/queue.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
class Queue {
|
||||
function __construct($key) { global $config;
|
||||
if ($config['queue']['enabled'] == 'fs') {
|
||||
$this->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);
|
||||
}
|
65
inc/route.php
Normal file
65
inc/route.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
// vichan's routing mechanism
|
||||
|
||||
// don't bother with that unless you use smart build or advanced build
|
||||
// you can use those parts for your own implementations though :^)
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
function route($path) { global $config;
|
||||
$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['/%b/index.rss'] = 'sb_catalog';
|
||||
$entrypoints['/sitemap.xml'] = 'sb_sitemap';
|
||||
|
||||
$entrypoints = array_merge($entrypoints, $config['controller_entrypoints']);
|
||||
|
||||
$reached = false;
|
||||
|
||||
list($request) = explode('?', $path);
|
||||
|
||||
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);
|
||||
|
||||
$matches = null;
|
||||
|
||||
if (preg_match ($id, $request, $matches)) {
|
||||
array_shift($matches);
|
||||
|
||||
$reached = array($fun, $matches);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $reached;
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue