diff --git a/.gitmodules b/.gitmodules index 73f90e0a..df07fdf3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "js/wPaint"] path = js/wPaint url = https://github.com/vichan-devel/wPaint.git + branch = master + +[submodule "inc/lib/parsedown"] + path = inc/lib/parsedown + url = https://github.com/vichan-devel/parsedown + branch = master diff --git a/README.md b/README.md index 6e153ab7..8881e6e5 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Installation Please remember to change the administrator account password. -See also: [Configuration Basics](http://tinyboard.org/docs/?p=Config). +See also: [Configuration Basics](https://web.archive.org/web/20121003095922/http://tinyboard.org/docs/?p=Config). Upgrade ------- diff --git a/inc/api.php b/inc/api.php index 57e4d367..b280c25b 100644 --- a/inc/api.php +++ b/inc/api.php @@ -32,6 +32,7 @@ class Api { 'images' => 'images', 'sticky' => 'sticky', 'locked' => 'locked', + 'cycle' => 'cyclical', 'bump' => 'last_modified', 'embed' => 'embed', ); @@ -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) { diff --git a/inc/bans.php b/inc/bans.php index 87e06e28..c468eb64 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -166,7 +166,7 @@ class Bans { if ($ban['post']) { $post = json_decode($ban['post']); - $ban['message'] = $post->body; + $ban['message'] = isset($post->body) ? $post->body : 0; } unset($ban['ipstart'], $ban['ipend'], $ban['post'], $ban['creator']); diff --git a/inc/captcha/captcha.php b/inc/captcha/captcha.php new file mode 100644 index 00000000..7c54fa3c --- /dev/null +++ b/inc/captcha/captcha.php @@ -0,0 +1,357 @@ +width = $left; + $this->height = $top; + + $this->charset = preg_split('//u', $charset); + + $this->style = ""; + + for ($i = 0; $i < $len; $i++) { + $this->content[] = array(mb_substr($text, $i, 1, 'utf-8'), "top" => $top / 2 - $top / 4, + "left" => $left/10 + 9*$left*$i/10/$len, + "position" => "absolute"); + } + + $this->color = "hsla(".rand(1,360).", 76%, 78%, 1)"; + + $this->add_junk(); + $this->mutate_sizes(); + $this->mutate_positions(); + $this->mutate_transform(); + $this->mutate_anchors(); + $this->randomize(); + $this->mutate_containers(); + $this->mutate_margins(); + $this->mutate_styles(); + $this->randomize(); + } + + function mutate_sizes() { + foreach ($this->content as &$v) { + if (!isset ($v['font-size'])) + $v['font-size'] = rand($this->height/3 - 4, $this->height/3 + 8); + } + } + function mutate_positions() { + foreach ($this->content as &$v) { + $v['top'] += rand(-10,10); + $v['left'] += rand(-10,10); + } + } + function mutate_transform() { + $fromto = array('6'=>'9', '9'=>'6', '8'=>'8', '0'=>'0', + 'z'=>'z', 's'=>'s', 'n'=>'u', 'u'=>'n', + 'a'=>'ɐ', 'e'=>'ə', 'p'=>'d', 'd'=>'p', + 'A'=>'∀', 'E'=>'∃', 'H'=>'H', 'o'=>'o', + 'O'=>'O'); + + foreach ($this->content as &$v) { + $basefrom = -20; + $baseto = 20; + + if (isset($fromto[$v[0]]) && rand(0,1)) { + $v[0] = $fromto[$v[0]]; + $basefrom = 160; + $baseto = 200; + } + + $v['transform'] = 'rotate('.rand($basefrom,$baseto).'deg)'; + $v['-ms-transform'] = 'rotate('.rand($basefrom,$baseto).'deg)'; + $v['-webkit-transform'] = 'rotate('.rand($basefrom,$baseto).'deg)'; + } + } + function randomize(&$a = false) { + if ($a === false) { + $a = &$this->content; + } + + shuffle($a); + + foreach ($a as &$v) { + $this->shuffle_assoc($v); + + if (is_array ($v[0])) { + $this->randomize($v[0]); + } + } + } + + function add_junk() { + $count = rand(200, 300); + + while ($count--) { + $elem = array(); + + $elem['top'] = rand(0, $this->height); + $elem['left'] = rand(0, $this->width); + + $elem['position'] = 'absolute'; + + $elem[0] = $this->charset[rand(0, count($this->charset)-1)]; + + switch($t = rand (0,9)) { + case 0: + $elem['display'] = 'none'; break; + case 1: + $elem['top'] = rand(-60, -90); break; + case 2: + $elem['left'] = rand(-40, -70); break; + case 3: + $elem['top'] = $this->height + rand(10, 60); break; + case 4: + $elem['left'] = $this->width + rand(10, 60); break; + case 5: + $elem['color'] = $this->color; break; + case 6: + $elem['visibility'] = 'hidden'; break; + case 7: + $elem['height'] = rand(0,2); + $elem['overflow'] = 'hidden'; break; + case 8: + $elem['width'] = rand(0,1); + $elem['overflow'] = 'hidden'; break; + case 9: + $elem['font-size'] = rand(2, 6); break; + } + + $this->content[] = $elem; + } + } + + function mutate_anchors() { + foreach ($this->content as &$elem) { + if (rand(0,1)) { + $elem['right'] = $this->width - $elem['left'] - (int)(0.5*$elem['font-size']); + unset($elem['left']); + } + if (rand(0,1)) { + $elem['bottom'] = $this->height - $elem['top'] - (int)(1.5*$elem['font-size']); + unset($elem['top']); + } + } + } + + function mutate_containers() { + for ($i = 0; $i <= 80; $i++) { + $new = []; + $new['width'] = rand(0, $this->width*2); + $new['height'] = rand(0, $this->height*2); + $new['top'] = rand(-$this->height * 2, $this->height * 2); + $new['bottom'] = $this->height - ($new['top'] + $new['height']); + $new['left'] = rand(-$this->width * 2, $this->width * 2); + $new['right'] = $this->width - ($new['left'] + $new['width']); + + $new['position'] = 'absolute'; + + $new[0] = []; + + $cnt = rand(0,10); + for ($j = 0; $j < $cnt; $j++) { + $elem = array_pop($this->content); + if (!$elem) break; + + if (isset($elem['top'])) $elem['top'] -= $new['top']; + if (isset($elem['bottom'])) $elem['bottom'] -= $new['bottom']; + if (isset($elem['left'])) $elem['left'] -= $new['left']; + if (isset($elem['right'])) $elem['right'] -= $new['right']; + + $new[0][] = $elem; + } + + if (rand (0,1)) unset($new['top']); + else unset($new['bottom']); + if (rand (0,1)) unset($new['left']); + else unset($new['right']); + + $this->content[] = $new; + + shuffle($this->content); + } + } + + function mutate_margins(&$a = false) { + if ($a === false) { + $a = &$this->content; + } + + foreach ($a as &$v) { + $ary = ['top', 'left', 'bottom', 'right']; + shuffle($ary); + $cnt = rand(0,4); + $ary = array_slice($ary, 0, $cnt); + + foreach ($ary as $prop) { + $margin = rand(-1000, 1000); + + $v['margin-'.$prop] = $margin; + + if (isset($v[$prop])) { + $v[$prop] -= $margin; + } + } + + if (is_array($v[0])) { + $this->mutate_margins($v[0]); + } + } + } + + function mutate_styles(&$a = false) { + if ($a === false) { + $a = &$this->content; + } + + foreach ($a as &$v) { + $content = $v[0]; + unset($v[0]); + $styles = array_splice($v, 0, rand(0, 6)); + $v[0] = $content; + + $id_or_class = rand(0,1); + $param = $id_or_class ? "id" : "class"; + $prefix = $id_or_class ? "#" : "."; + $genname = "zz-".base_convert(rand(1,999999999), 10, 36); + + if ($styles || rand(0,1)) { + $this->style .= $prefix.$genname."{"; + $this->style .= $this->rand_whitespace(); + + foreach ($styles as $k => $val) { + if (is_int($val)) { + $val = "".$val."px"; + } + + $this->style .= "$k:"; + $this->style .= $this->rand_whitespace(); + $this->style .= "$val;"; + $this->style .= $this->rand_whitespace(); + } + $this->style .= "}"; + $this->style .= $this->rand_whitespace(); + } + + $v[$param] = $genname; + + if (is_array($v[0])) { + $this->mutate_styles($v[0]); + } + } + } + + function to_html(&$a = false) { + $inside = true; + + if ($a === false) { + if ($this->style) { + echo ""; + } + + echo "
"; + $a = &$this->content; + $inside = false; + } + + foreach ($a as &$v) { + $letter = $v[0]; + + unset ($v[0]); + + echo "rand_whitespace(1); + + if (isset ($v['id'])) { + echo "id='$v[id]'"; + echo $this->rand_whitespace(1); + + unset ($v['id']); + } + if (isset ($v['class'])) { + echo "class='$v[class]'"; + echo $this->rand_whitespace(1); + + unset ($v['class']); + } + + echo "style='"; + + foreach ($v as $k => $val) { + if (is_int($val)) { + $val = "".$val."px"; + } + + echo "$k:"; + echo $this->rand_whitespace(); + echo "$val;"; + echo $this->rand_whitespace(); + + } + + echo "'>"; + echo $this->rand_whitespace(); + + if (is_array ($letter)) { + $this->to_html($letter); + } + else { + echo $letter; + } + + echo "
"; + } + + if (!$inside) { + echo ""; + } + + } + + function rand_whitespace($r = 0) { + switch (rand($r,4)) { + case 0: + return ""; + case 1: + return "\n"; + case 2: + return "\t"; + case 3: + return " "; + case 4: + return " "; + } + } + + + + function shuffle_assoc(&$array) { + $keys = array_keys($array); + + shuffle($keys); + + foreach($keys as $key) { + $new[$key] = $array[$key]; + } + + $array = $new; + + return true; + } +} + +//$charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789卐"; + +//(new CzaksCaptcha("hotwheels", 300, 80, $charset))->to_html(); +?> diff --git a/inc/captcha/config.php b/inc/captcha/config.php new file mode 100644 index 00000000..568c06c5 --- /dev/null +++ b/inc/captcha/config.php @@ -0,0 +1,16 @@ + 'SET NAMES utf8')); + + +// Captcha expiration: +$expires_in = 120; // 120 seconds + +// Captcha dimensions: +$width = 250; +$height = 80; + +// Captcha length: +$length = 6; diff --git a/inc/captcha/dbschema.sql b/inc/captcha/dbschema.sql new file mode 100644 index 00000000..504ea10c --- /dev/null +++ b/inc/captcha/dbschema.sql @@ -0,0 +1,9 @@ +SET NAMES utf8; + +CREATE TABLE `captchas` ( + `cookie` VARCHAR(50), + `extra` VARCHAR(200), + `text` VARCHAR(255), + `created_at` INT(11), + PRIMARY KEY (cookie, extra) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; diff --git a/inc/captcha/entrypoint.php b/inc/captcha/entrypoint.php new file mode 100644 index 00000000..3b468a2a --- /dev/null +++ b/inc/captcha/entrypoint.php @@ -0,0 +1,85 @@ +prepare("DELETE FROM `captchas` WHERE `created_at` < ?")->execute([time() - $expires_in]); +} + +switch ($mode) { +// Request: GET entrypoint.php?mode=get&extra=1234567890 +// Response: JSON: cookie => "generatedcookie", captchahtml => "captchahtml", expires_in => 120 +case "get": + if (!isset ($_GET['extra'])) { + die(); + } + + header("Content-type: application/json"); + + $extra = $_GET['extra']; + + require_once("config.php"); + + $text = rand_string($length, $extra); + + $captcha = new CzaksCaptcha($text, $width, $height, $extra); + + $cookie = rand_string(20, "abcdefghijklmnopqrstuvwxyz"); + + ob_start(); + $captcha->to_html(); + $html = ob_get_contents(); + ob_end_clean(); + + $query = $pdo->prepare("INSERT INTO `captchas` (`cookie`, `extra`, `text`, `created_at`) VALUES (?, ?, ?, ?)"); + $query->execute( [$cookie, $extra, $text, time()]); + + echo json_encode(["cookie" => $cookie, "captchahtml" => $html, "expires_in" => $expires_in]); + + break; + +// Request: GET entrypoint.php?mode=check&cookie=generatedcookie&extra=1234567890&text=captcha +// Response: 0 OR 1 +case "check": + if (!isset ($_GET['mode']) + || !isset ($_GET['cookie']) + || !isset ($_GET['extra']) + || !isset ($_GET['text'])) { + die(); + } + + require_once("config.php"); + + cleanup($pdo, $expires_in); + + $query = $pdo->prepare("SELECT * FROM `captchas` WHERE `cookie` = ? AND `extra` = ?"); + $query->execute([$_GET['cookie'], $_GET['extra']]); + + $ary = $query->fetchAll(); + + if (!$ary) { + echo "0"; + } + else { + $query = $pdo->prepare("DELETE FROM `captchas` WHERE `cookie` = ? AND `extra` = ?"); + $query->execute([$_GET['cookie'], $_GET['extra']]); + + if ($ary[0]['text'] !== $_GET['text']) { + echo "0"; + } + else { + echo "1"; + } + } + + break; +} diff --git a/inc/captcha/readme.md b/inc/captcha/readme.md new file mode 100644 index 00000000..8b1f538a --- /dev/null +++ b/inc/captcha/readme.md @@ -0,0 +1,10 @@ +I integrated this from: https://github.com/ctrlcctrlv/infinity/commit/62a6dac022cb338f7b719d0c35a64ab3efc64658 + +First import the captcha/dbschema.sql in your database it is no longer required. + +In inc/captcha/config.php change the database_name database_user database_password to your own settings. + +Add js/captcha.js in your instance-config.php or config.php + +Go to Line 305 in the /inc/config file and copy the settings in instance config, while changing the url to your website. +Go to the line beneath it if you only want to enable it when posting a new thread. diff --git a/inc/config.php b/inc/config.php index c0e967af..93ce8321 100644 --- a/inc/config.php +++ b/inc/config.php @@ -103,7 +103,7 @@ /* * ==================== - * Cache settings + * Cache, lock and queue settings * ==================== */ @@ -120,6 +120,7 @@ // $config['cache']['enabled'] = 'apc'; // $config['cache']['enabled'] = 'memcached'; // $config['cache']['enabled'] = 'redis'; + // $config['cache']['enabled'] = 'fs'; // Timeout for cached objects such as posts and HTML. $config['cache']['timeout'] = 60 * 60 * 48; // 48 hours @@ -142,6 +143,12 @@ // (this file will be explicitly loaded during cache hit, but not during cache miss). $config['cache_config'] = false; + // Define a lock driver. + $config['lock']['enabled'] = 'fs'; + + // Define a queue driver. + $config['queue']['enabled'] = 'fs'; // xD + /* * ==================== * Cookie settings @@ -189,7 +196,11 @@ // Prevents most Tor exit nodes from making posts. Recommended, as a lot of abuse comes from Tor because // of the strong anonymity associated with it. - $config['dnsbl'][] = array('tor.dnsbl.sectoor.de', 1); + // Example: $config['dnsbl'][] = 'another.blacklist.net'; // + // $config['dnsbl'][] = array('tor.dnsbl.sectoor.de', 1); //sectoor.de site is dead. the number stands for (an) ip adress(es) I guess. + + // Replacement for sectoor.de + $config['dnsbl'][] = array('rbl.efnet.org', 4); // http://www.sorbs.net/using.shtml // $config['dnsbl'][] = array('dnsbl.sorbs.net', array(2, 3, 4, 5, 6, 7, 8, 9)); @@ -275,8 +286,9 @@ 'lock', 'raw', 'embed', - 'recaptcha_challenge_field', - 'recaptcha_response_field', + 'g-recaptcha-response', + 'captcha_cookie', + 'captcha_text', 'spoiler', 'page', 'file_url', @@ -292,6 +304,26 @@ // Public and private key pair from https://www.google.com/recaptcha/admin/create $config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f'; $config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_'; + + // Enable Custom Captcha you need to change a couple of settings + //Read more at: /captcha/instructions.md + $config['captcha'] = array(); + + // Enable custom captcha provider + $config['captcha']['enabled'] = false; + + //New thread captcha + //Require solving a captcha to post a thread. + //Default off. + $config['new_thread_capt'] = false; + + // Custom captcha get provider path (if not working get the absolute path aka your url.) + $config['captcha']['provider_get'] = '../inc/captcha/entrypoint.php'; + // Custom captcha check provider path + $config['captcha']['provider_check'] = '../inc/captcha/entrypoint.php'; + + // Custom captcha extra field (eg. charset) + $config['captcha']['extra'] = 'abcdefghijklmnopqrstuvwxyz'; // Ability to lock a board for normal users and still allow mods to post. Could also be useful for making an archive board $config['board_locked'] = false; @@ -511,6 +543,14 @@ // 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; + $config['early_404_staged'] = false; + // 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. @@ -650,7 +690,7 @@ */ // Maximum number of images allowed. Increasing this number enabled multi image. // If you make it more than 1, make sure to enable the below script for the post form to change. - // $config['additional_javascript'][] = 'js/multi_image.js'; + // $config['additional_javascript'][] = 'js/multi-image.js'; $config['max_images'] = 1; // Method to use for determing the max filesize. @@ -766,7 +806,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. @@ -807,8 +847,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; @@ -931,8 +980,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( @@ -978,6 +1027,7 @@ */ // Additional Javascript files to include on board index and thread pages. See js/ for available scripts. + $config['additional_javascript'][] = 'js/jquery.min.js'; $config['additional_javascript'][] = 'js/inline-expanding.js'; // $config['additional_javascript'][] = 'js/local-time.js'; @@ -991,6 +1041,7 @@ // $config['additional_javascript'][] = 'js/auto-reload.js'; // $config['additional_javascript'][] = 'js/post-hover.js'; // $config['additional_javascript'][] = 'js/style-select.js'; + // $config['additional_javascript'][] = 'js/captcha.js'; // Where these script files are located on the web. Defaults to $config['root']. // $config['additional_javascript_url'] = 'http://static.example.org/tinyboard-javascript-stuff/'; @@ -1001,6 +1052,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 @@ -1076,6 +1131,7 @@ $config['error']['toomanycross'] = _('Too many cross-board links; post discarded.'); $config['error']['nodelete'] = _('You didn\'t select anything to delete.'); $config['error']['noreport'] = _('You didn\'t select anything to report.'); + $config['error']['invalidreport'] = _('The reason was too long.'); $config['error']['toomanyreports'] = _('You can\'t report that many posts at once.'); $config['error']['invalidpassword'] = _('Wrong password…'); $config['error']['invalidimg'] = _('Invalid image.'); @@ -1196,16 +1252,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(); /* * ==================== @@ -1238,6 +1352,8 @@ $config['mod']['link_bumpunlock'] = '[-Sage]'; $config['mod']['link_editpost'] = '[Edit]'; $config['mod']['link_move'] = '[Move]'; + $config['mod']['link_cycle'] = '[Cycle]'; + $config['mod']['link_uncycle'] = '[-Cycle]'; // Moderator capcodes. $config['capcode'] = ' ## %s'; @@ -1381,6 +1497,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 @@ -1491,6 +1610,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(); @@ -1537,25 +1659,33 @@ /* * ==================== - * Public post search + * Public pages * ==================== */ + + // Public post search settings $config['search'] = array(); // Enable the search form $config['search']['enable'] = false; + // Enable search in the board index. + $config['board_search'] = 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; /* * ==================== @@ -1591,6 +1721,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 @@ -1687,3 +1856,9 @@ // 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'; diff --git a/inc/controller.php b/inc/controller.php new file mode 100644 index 00000000..02e33443 --- /dev/null +++ b/inc/controller.php @@ -0,0 +1,108 @@ + $config['max_pages']) return false; + $config['try_smarter'] = true; + $build_pages = array($page); + buildIndex("skip"); + return true; +} + +function sb_api_board($b, $page = 0) { $page = (int)$page; + return sb_board($b, $page + 1); +} + +function sb_thread($b, $thread, $slugcheck = false) { global $config; $thread = (int)$thread; + if ($thread < 1) return false; + + if (!preg_match('/^'.$config['board_regex'].'$/u', $b)) return false; + + if (Cache::get("thread_exists_".$b."_".$thread) == "no") return false; + + $query = prepare(sprintf("SELECT MAX(`id`) AS `max` FROM ``posts_%s``", $b)); + if (!$query->execute()) return false; + + $s = $query->fetch(PDO::FETCH_ASSOC); + $max = $s['max']; + + if ($thread > $max) return false; + + $query = prepare(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL", $b)); + $query->bindValue(':id', $thread); + + if (!$query->execute() || !$query->fetch(PDO::FETCH_ASSOC) ) { + Cache::set("thread_exists_".$b."_".$thread, "no", 3600); + return false; + } + + if ($slugcheck && $config['slugify']) { + global $request; + + $link = link_for(array("id" => $thread), $slugcheck === 50, array("uri" => $b)); + $link = "/".$b."/".$config['dir']['res'].$link; + + if ($link != $request) { + header("Location: $link", true, 301); + die(); + } + } + + if ($slugcheck == 50) { // Should we really generate +50 page? Maybe there are not enough posts anyway + global $request; + $r = str_replace("+50", "", $request); + $r = substr($r, 1); // Cut the slash + + if (file_exists($r)) return false; + } + + if (!openBoard($b)) return false; + buildThread($thread); + return true; +} + +function sb_thread_slugcheck($b, $thread) { + return sb_thread($b, $thread, true); +} +function sb_thread_slugcheck50($b, $thread) { + return sb_thread($b, $thread, 50); +} + +function sb_api($b) { global $config, $build_pages; + if (!openBoard($b)) return false; + $config['try_smarter'] = true; + $build_pages = array(-1); + buildIndex(); + return true; +} + +function sb_ukko() { + rebuildTheme("ukko", "post-thread"); + return true; +} + +function sb_catalog($b) { + if (!openBoard($b)) return false; + + rebuildTheme("catalog", "post-thread", $b); + return true; +} + +function sb_recent() { + rebuildTheme("recent", "post-thread"); + return true; +} + +function sb_sitemap() { + rebuildTheme("sitemap", "all"); + return true; +} + diff --git a/inc/display.php b/inc/display.php index 90cf3434..58cb4805 100644 --- a/inc/display.php +++ b/inc/display.php @@ -94,21 +94,16 @@ function error($message, $priority = true, $debug_stuff = false) { $debug_stuff['backtrace'] = debug_backtrace(); } - // Return the bad request header, necessary for AJAX posts - // czaks: is it really so? the ajax errors only work when this is commented out - // better yet use it when ajax is disabled - if (!isset ($_POST['json_response'])) { - header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request'); - } - - // Is there a reason to disable this? if (isset($_POST['json_response'])) { header('Content-Type: text/json; charset=utf-8'); die(json_encode(array( 'error' => $message ))); } - + else { + header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request'); + } + $pw = $config['db']['password']; $debug_callback = function(&$item) use (&$debug_callback, $pw) { if (is_array($item)) { diff --git a/inc/functions.php b/inc/functions.php index 515e3e55..e7e5aa14 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -18,6 +18,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'; } @@ -89,6 +95,8 @@ function loadConfig() { 'db', 'api', 'cache', + 'lock', + 'queue', 'cookies', 'error', 'dir', @@ -125,7 +133,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'; @@ -136,13 +144,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) { @@ -281,9 +289,6 @@ function loadConfig() { if ($config['syslog']) openlog('tinyboard', LOG_ODELAY, LOG_SYSLOG); // open a connection to sysem logger - if ($config['recaptcha']) - require_once 'inc/lib/recaptcha/recaptchalib.php'; - if ($config['cache']['enabled']) require_once 'inc/cache.php'; @@ -522,7 +527,8 @@ function setupBoard($array) { $board = array( 'uri' => $array['uri'], 'title' => $array['title'], - 'subtitle' => $array['subtitle'] + 'subtitle' => $array['subtitle'], + #'indexed' => $array['indexed'], ); // older versions @@ -547,14 +553,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(); @@ -878,7 +889,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, @@ -1016,7 +1027,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'])) { @@ -1056,6 +1067,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 { @@ -1128,6 +1145,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. @@ -1139,8 +1158,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); @@ -1162,19 +1183,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)); @@ -1263,7 +1287,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']); @@ -1274,6 +1298,39 @@ 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)); + + if ($config['early_404_staged']) { + $page = $config['early_404_page']; + $iter = 0; + } + else { + $page = 1; + } + + while ($post = $query->fetch(PDO::FETCH_ASSOC)) { + if ($post['reply_count'] < $page*$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)"); + } + + if ($config['early_404_staged']) { + $iter++; + + if ($iter == $config['threads_per_page']) { + $page++; + $iter = 0; + } + } + } } } @@ -1287,7 +1344,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 = ''; @@ -1318,6 +1376,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']); @@ -1357,7 +1416,10 @@ function index($page, $mod=false) { } $threads[] = $thread; - $body .= $thread->build(true); + + if (!$brief) { + $body .= $thread->build(true); + } } if ($config['file_board']) { @@ -1578,27 +1640,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; @@ -1609,17 +1672,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']); @@ -1627,13 +1694,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); @@ -1647,13 +1715,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); @@ -1706,13 +1774,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; @@ -1845,7 +1915,11 @@ function extract_modifiers($body) { return $modifiers; } -function markup(&$body, $track_cites = false) { +function remove_modifiers($body) { + return preg_replace('@(.+?)@usm', '', $body); +} + +function markup(&$body, $track_cites = false, $op = false) { global $board, $config, $markup_urls; $modifiers = extract_modifiers($body); @@ -1941,7 +2015,7 @@ function markup(&$body, $track_cites = false) { } if (isset($cited_posts[$cite])) { - $replacement = '' . '>>' . $cite . @@ -2040,7 +2114,7 @@ function markup(&$body, $track_cites = false) { $replacement = '' . '>>>/' . $_board . '/' . $cite . ''; @@ -2144,16 +2218,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; @@ -2177,7 +2242,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)); @@ -2212,26 +2279,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); @@ -2372,7 +2439,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}"]; @@ -2461,7 +2528,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 @@ -2597,7 +2664,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"); @@ -2672,3 +2739,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'); + } +} diff --git a/inc/lib/Twig/Extensions/Extension/Tinyboard.php b/inc/lib/Twig/Extensions/Extension/Tinyboard.php index 028db438..3d964c71 100644 --- a/inc/lib/Twig/Extensions/Extension/Tinyboard.php +++ b/inc/lib/Twig/Extensions/Extension/Tinyboard.php @@ -17,6 +17,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension new Twig_SimpleFilter('extension', 'twig_extension_filter'), new Twig_SimpleFilter('sprintf', 'sprintf'), new Twig_SimpleFilter('capcode', 'capcode'), + new Twig_SimpleFilter('remove_modifiers', 'remove_modifiers'), new Twig_SimpleFilter('hasPermission', 'twig_hasPermission_filter'), new Twig_SimpleFilter('date', 'twig_date_filter'), new Twig_SimpleFilter('poster_id', 'poster_id'), diff --git a/inc/lib/recaptcha/LICENSE b/inc/lib/recaptcha/LICENSE deleted file mode 100644 index b612f71f..00000000 --- a/inc/lib/recaptcha/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net -AUTHORS: - Mike Crawford - Ben Maurer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/inc/lib/recaptcha/recaptchalib.php b/inc/lib/recaptcha/recaptchalib.php deleted file mode 100644 index 32c4f4d7..00000000 --- a/inc/lib/recaptcha/recaptchalib.php +++ /dev/null @@ -1,277 +0,0 @@ - $value ) - $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; - - // Cut the last '&' - $req=substr($req,0,strlen($req)-1); - return $req; -} - - - -/** - * Submits an HTTP POST to a reCAPTCHA server - * @param string $host - * @param string $path - * @param array $data - * @param int port - * @return array response - */ -function _recaptcha_http_post($host, $path, $data, $port = 80) { - - $req = _recaptcha_qsencode ($data); - - $http_request = "POST $path HTTP/1.0\r\n"; - $http_request .= "Host: $host\r\n"; - $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; - $http_request .= "Content-Length: " . strlen($req) . "\r\n"; - $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; - $http_request .= "\r\n"; - $http_request .= $req; - - $response = ''; - if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { - die ('Could not open socket'); - } - - fwrite($fs, $http_request); - - while ( !feof($fs) ) - $response .= fgets($fs, 1160); // One TCP-IP packet - fclose($fs); - $response = explode("\r\n\r\n", $response, 2); - - return $response; -} - - - -/** - * Gets the challenge HTML (javascript and non-javascript version). - * This is called from the browser, and the resulting reCAPTCHA HTML widget - * is embedded within the HTML form it was called from. - * @param string $pubkey A public key for reCAPTCHA - * @param string $error The error given by reCAPTCHA (optional, default is null) - * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) - - * @return string - The HTML to be embedded in the user's form. - */ -function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) -{ - if ($pubkey == null || $pubkey == '') { - die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); - } - - if ($use_ssl) { - $server = RECAPTCHA_API_SECURE_SERVER; - } else { - $server = RECAPTCHA_API_SERVER; - } - - $errorpart = ""; - if ($error) { - $errorpart = "&error=" . $error; - } - return ' - - '; -} - - - - -/** - * A ReCaptchaResponse is returned from recaptcha_check_answer() - */ -class ReCaptchaResponse { - var $is_valid; - var $error; -} - - -/** - * Calls an HTTP POST function to verify if the user's guess was correct - * @param string $privkey - * @param string $remoteip - * @param string $challenge - * @param string $response - * @param array $extra_params an array of extra variables to post to the server - * @return ReCaptchaResponse - */ -function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) -{ - if ($privkey == null || $privkey == '') { - die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); - } - - if ($remoteip == null || $remoteip == '') { - die ("For security reasons, you must pass the remote ip to reCAPTCHA"); - } - - - - //discard spam submissions - if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { - $recaptcha_response = new ReCaptchaResponse(); - $recaptcha_response->is_valid = false; - $recaptcha_response->error = 'incorrect-captcha-sol'; - return $recaptcha_response; - } - - $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", - array ( - 'privatekey' => $privkey, - 'remoteip' => $remoteip, - 'challenge' => $challenge, - 'response' => $response - ) + $extra_params - ); - - $answers = explode ("\n", $response [1]); - $recaptcha_response = new ReCaptchaResponse(); - - if (trim ($answers [0]) == 'true') { - $recaptcha_response->is_valid = true; - } - else { - $recaptcha_response->is_valid = false; - $recaptcha_response->error = $answers [1]; - } - return $recaptcha_response; - -} - -/** - * gets a URL where the user can sign up for reCAPTCHA. If your application - * has a configuration page where you enter a key, you should provide a link - * using this function. - * @param string $domain The domain where the page is hosted - * @param string $appname The name of your application - */ -function recaptcha_get_signup_url ($domain = null, $appname = null) { - return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname)); -} - -function _recaptcha_aes_pad($val) { - $block_size = 16; - $numpad = $block_size - (strlen ($val) % $block_size); - return str_pad($val, strlen ($val) + $numpad, chr($numpad)); -} - -/* Mailhide related code */ - -function _recaptcha_aes_encrypt($val,$ky) { - if (! function_exists ("mcrypt_encrypt")) { - die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); - } - $mode=MCRYPT_MODE_CBC; - $enc=MCRYPT_RIJNDAEL_128; - $val=_recaptcha_aes_pad($val); - return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); -} - - -function _recaptcha_mailhide_urlbase64 ($x) { - return strtr(base64_encode ($x), '+/', '-_'); -} - -/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ -function recaptcha_mailhide_url($pubkey, $privkey, $email) { - if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { - die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . - "you can do so at http://www.google.com/recaptcha/mailhide/apikey"); - } - - - $ky = pack('H*', $privkey); - $cryptmail = _recaptcha_aes_encrypt ($email, $ky); - - return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); -} - -/** - * gets the parts of the email to expose to the user. - * eg, given johndoe@example,com return ["john", "example.com"]. - * the email is then displayed as john...@example.com - */ -function _recaptcha_mailhide_email_parts ($email) { - $arr = preg_split("/@/", $email ); - - if (strlen ($arr[0]) <= 4) { - $arr[0] = substr ($arr[0], 0, 1); - } else if (strlen ($arr[0]) <= 6) { - $arr[0] = substr ($arr[0], 0, 3); - } else { - $arr[0] = substr ($arr[0], 0, 4); - } - return $arr; -} - -/** - * Gets html to display an email address given a public an private key. - * to get a key, go to: - * - * http://www.google.com/recaptcha/mailhide/apikey - */ -function recaptcha_mailhide_html($pubkey, $privkey, $email) { - $emailparts = _recaptcha_mailhide_email_parts ($email); - $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); - - return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); - -} - - -?> diff --git a/inc/locale/uk_UA/LC_MESSAGES/javascript.po b/inc/locale/uk_UA/LC_MESSAGES/javascript.po new file mode 100644 index 00000000..68d96ce8 --- /dev/null +++ b/inc/locale/uk_UA/LC_MESSAGES/javascript.po @@ -0,0 +1,867 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-18 13:47+0200\n" +"PO-Revision-Date: 2016-06-19 17:30+0300\n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Last-Translator: kotobenko \n" +"Language-Team: \n" +"X-Generator: Poedit 1.8.8\n" + +#: ../../../../js/style-select.js:40 ../../../../js/style-select.js:41 +msgid "Style: " +msgstr "Стиль:" + +#: ../../../../js/hide-images.js:50 ../../../../js/upload-selection.js:51 +#: ../../../../js/quick-post-controls.js:30 ../../../../js/hide-images.js:51 +#: ../../../../js/quick-post-controls.js:32 +#: ../../../../js/upload-selection.js:61 +#: ../../../../js/upload-selection.js:69 +msgid "File" +msgstr "Файл" + +#: ../../../../js/hide-images.js:50 ../../../../js/hide-images.js:51 +msgid "hide" +msgstr "приховати" + +#: ../../../../js/hide-images.js:56 ../../../../js/hide-images.js:57 +#: ../../../../js/hide-images.js:63 +msgid "show" +msgstr "показати" + +#: ../../../../js/toggle-locked-threads.js:39 +#: ../../../../js/toggle-locked-threads.js:54 +#: ../../../../js/toggle-locked-threads.js:40 +#: ../../../../js/toggle-locked-threads.js:55 +#: ../../../../js/toggle-locked-threads.js:41 +#: ../../../../js/toggle-locked-threads.js:56 +#: ../../../../js/toggle-locked-threads.js:53 +#: ../../../../js/toggle-locked-threads.js:70 +msgid "Show locked threads" +msgstr "Показати закріплені нитки" + +#: ../../../../js/toggle-locked-threads.js:39 +#: ../../../../js/toggle-locked-threads.js:54 +#: ../../../../js/toggle-locked-threads.js:40 +#: ../../../../js/toggle-locked-threads.js:55 +#: ../../../../js/toggle-locked-threads.js:41 +#: ../../../../js/toggle-locked-threads.js:56 +#: ../../../../js/toggle-locked-threads.js:44 +#: ../../../../js/toggle-locked-threads.js:53 +#: ../../../../js/toggle-locked-threads.js:70 +msgid "Hide locked threads" +msgstr "Приховати закріплені нитки" + +#: ../../../../js/upload-selection.js:32 +#: ../../../../js/upload-selection.js:45 +#: ../../../../js/upload-selection.js:53 +msgid "URL" +msgstr "URL" + +#: ../../../../js/upload-selection.js:50 +#: ../../../../js/upload-selection.js:60 +#: ../../../../js/upload-selection.js:68 +msgid "Select" +msgstr "Вибрати" + +#: ../../../../js/upload-selection.js:53 +#: ../../../../js/upload-selection.js:63 +#: ../../../../js/upload-selection.js:71 +msgid "Remote" +msgstr "" + +#: ../../../../js/upload-selection.js:56 +#: ../../../../js/upload-selection.js:66 +#: ../../../../js/upload-selection.js:74 +msgid "Embed" +msgstr "Вбудувати" + +#: ../../../../js/upload-selection.js:59 +#: ../../../../js/upload-selection.js:69 +#: ../../../../js/upload-selection.js:77 +msgid "Oekaki" +msgstr "Оекакі" + +#: ../../../../js/toggle-images.js:41 ../../../../js/toggle-images.js:42 +#: ../../../../js/toggle-images.js:45 +msgid "hidden" +msgstr "" + +#: ../../../../js/toggle-images.js:57 ../../../../js/toggle-images.js:70 +#: ../../../../js/toggle-images.js:58 ../../../../js/toggle-images.js:71 +#: ../../../../js/toggle-images.js:86 +msgid "Show images" +msgstr "Показати зображення" + +#: ../../../../js/toggle-images.js:57 ../../../../js/toggle-images.js:70 +#: ../../../../js/toggle-images.js:58 ../../../../js/toggle-images.js:71 +#: ../../../../js/toggle-images.js:63 ../../../../js/toggle-images.js:86 +msgid "Hide images" +msgstr "Приховати зображення" + +#: ../../../../js/quick-post-controls.js:27 +#: ../../../../js/quick-post-controls.js:29 +msgid "Password" +msgstr "Пароль" + +#: ../../../../js/quick-post-controls.js:29 +#: ../../../../js/quick-post-controls.js:31 +msgid "Delete file only" +msgstr "Видалити тільки файл" + +#: ../../../../js/quick-post-controls.js:31 +#: ../../../../js/quick-post-controls.js:33 +msgid "Delete" +msgstr "Видалити" + +#: ../../../../js/quick-post-controls.js:35 +#: ../../../../js/quick-post-controls.js:37 +#: ../../../../js/mod/ban-list.js:40 +msgid "Reason" +msgstr "Причина" + +#: ../../../../js/quick-post-controls.js:37 +#: ../../../../js/quick-post-controls.js:39 +msgid "Report" +msgstr "Повідомити адміністрацію" + +#: ../../../../js/expand.js:20 ../../../../js/expand.js:22 +msgid "Click reply to view." +msgstr "Натисніть «Відповісти», щоб переглянути нитку повністю" + +#: ../../../../js/expand.js:20 ../../../../js/expand.js:22 +#: ../../../../js/live-index.js:72 ../../../../js/live-index.js:83 +msgid "Click to expand" +msgstr "Показати повністю" + +#: ../../../../js/expand.js:44 ../../../../js/expand.js:46 +#: ../../../../js/expand.js:50 +msgid "Hide expanded replies" +msgstr "Приховати розгорнуті дописи" + +#: ../../../../js/oekaki.js:10 +msgid "Brush size" +msgstr "Розмір пензля" + +#: ../../../../js/oekaki.js:10 +msgid "Set text" +msgstr "Додати текст" + +#: ../../../../js/oekaki.js:10 +msgid "Clear" +msgstr "Очистити" + +#: ../../../../js/oekaki.js:10 +msgid "Save" +msgstr "Зберегти" + +#: ../../../../js/oekaki.js:10 +msgid "Load" +msgstr "Завантажити" + +#: ../../../../js/oekaki.js:11 +msgid "Toggle eraser" +msgstr "Увімкнути стирачку" + +#: ../../../../js/oekaki.js:11 +msgid "Get color" +msgstr "Отримати колір" + +#: ../../../../js/oekaki.js:11 +msgid "Fill" +msgstr "Залити" + +#: ../../../../js/oekaki.js:12 +msgid "Use oekaki instead of file?" +msgstr "Використати оекакі замість файла?" + +#: ../../../../js/oekaki.js:21 +msgid "Edit in oekaki" +msgstr "Відкрити в редакторі оекакі" + +#: ../../../../js/oekaki.js:152 +msgid "Enter some text" +msgstr "Введіть текст" + +#: ../../../../js/oekaki.js:153 +msgid "Enter font or leave empty" +msgstr "Введіть назву шрифту або залиште порожнім" + +#: ../../../../js/forced-anon.js:59 ../../../../js/forced-anon.js:65 +#: ../../../../js/forced-anon.js:69 ../../../../js/forced-anon.js:60 +#: ../../../../js/forced-anon.js:66 ../../../../js/forced-anon.js:70 +#: ../../../../js/forced-anon.js:61 ../../../../js/forced-anon.js:67 +#: ../../../../js/forced-anon.js:71 ../../../../js/forced-anon.js:73 +#: ../../../../js/forced-anon.js:81 ../../../../js/forced-anon.js:85 +msgid "Forced anonymity" +msgstr "Примусова анонімність" + +#: ../../../../js/forced-anon.js:59 ../../../../js/forced-anon.js:65 +#: ../../../../js/forced-anon.js:60 ../../../../js/forced-anon.js:66 +#: ../../../../js/forced-anon.js:61 ../../../../js/forced-anon.js:67 +#: ../../../../js/forced-anon.js:73 ../../../../js/forced-anon.js:81 +msgid "enabled" +msgstr "увімкнено" + +#: ../../../../js/forced-anon.js:59 ../../../../js/forced-anon.js:69 +#: ../../../../js/forced-anon.js:60 ../../../../js/forced-anon.js:70 +#: ../../../../js/forced-anon.js:61 ../../../../js/forced-anon.js:71 +#: ../../../../js/forced-anon.js:73 ../../../../js/forced-anon.js:85 +msgid "disabled" +msgstr "вимкнено" + +#: ../../../../js/local-time.js:40 ../../../../js/local-time.js:41 +#: ../../../../js/local-time.js:30 ../../../../templates/main.js:63 +msgid "Sun" +msgstr "НД" + +#: ../../../../js/local-time.js:40 ../../../../js/local-time.js:41 +#: ../../../../js/local-time.js:30 ../../../../templates/main.js:63 +msgid "Mon" +msgstr "ПН" + +#: ../../../../js/local-time.js:40 ../../../../js/local-time.js:41 +#: ../../../../js/local-time.js:30 ../../../../templates/main.js:63 +msgid "Tue" +msgstr "ВТ" + +#: ../../../../js/local-time.js:40 ../../../../js/local-time.js:41 +#: ../../../../js/local-time.js:30 ../../../../templates/main.js:63 +msgid "Wed" +msgstr "СР" + +#: ../../../../js/local-time.js:40 ../../../../js/local-time.js:41 +#: ../../../../js/local-time.js:30 ../../../../templates/main.js:63 +msgid "Thu" +msgstr "ЧТ" + +#: ../../../../js/local-time.js:40 ../../../../js/local-time.js:41 +#: ../../../../js/local-time.js:30 ../../../../templates/main.js:63 +msgid "Fri" +msgstr "ПТ" + +#: ../../../../js/local-time.js:40 ../../../../js/local-time.js:41 +#: ../../../../js/local-time.js:30 ../../../../templates/main.js:63 +msgid "Sat" +msgstr "СБ" + +#: ../../../../js/catalog-link.js:21 ../../../../js/catalog-link.js:32 +#: ../../../../js/catalog-link.js:40 ../../../../js/catalog-link.js:33 +#: ../../../../js/catalog-link.js:44 ../../../../js/catalog-link.js:52 +#: ../../../../js/catalog-link.js:28 ../../../../js/catalog-link.js:39 +#: ../../../../js/catalog-link.js:47 +msgid "Catalog" +msgstr "Каталог" + +#: ../../../../js/quick-reply.js:21 ../../../../js/quick-reply-old.js:21 +#: ../../../../js/quick-reply-old.js:23 +msgid "Submit" +msgstr "Розмістити" + +#: ../../../../js/quick-reply.js:31 ../../../../js/quick-reply-old.js:31 +#: ../../../../js/quick-reply-old.js:33 +msgid "Quick reply" +msgstr "Відповісти швидко" + +#: ../../../../js/quick-reply.js:33 ../../../../js/quick-reply-old.js:33 +#: ../../../../js/quick-reply-old.js:35 +#, python-brace-format +msgid "Posting mode: Replying to >>{0}" +msgstr "Режим розміщення: Відповідь >>{0}" + +#: ../../../../js/quick-reply.js:33 ../../../../js/quick-reply-old.js:33 +#: ../../../../js/quick-reply-old.js:35 +msgid "Return" +msgstr "Повернутися" + +#: ../../../../js/expand-all-images.js:20 +#: ../../../../js/expand-all-images.js:21 +#: ../../../../js/expand-all-images.js:22 +#: ../../../../js/expand-all-images.js:23 +msgid "Expand all images" +msgstr "Розгорнути всі зображення" + +#: ../../../../templates/main.js:6 +msgid "Hello!" +msgstr "Привіт!" + +#: ../../../../templates/main.js:18 +#, python-brace-format +msgid "{0} users" +msgstr "{0} користувачів" + +#: ../../../../templates/themes/ukko/ukko.js:28 +#: ../../../../templates/themes/ukko/ukko.js:39 +#: ../../../../templates/themes/ukko/ukko.js:29 +#: ../../../../templates/themes/ukko/ukko.js:40 +#: ../../../../templates/themes/ukko/ukko.js:52 +#: ../../../../templates/themes/ukko/ukko.js:63 +msgid "(hide threads from this board)" +msgstr "(приховати нитки цієї дошки)" + +#: ../../../../templates/themes/ukko/ukko.js:32 +#: ../../../../templates/themes/ukko/ukko.js:44 +#: ../../../../templates/themes/ukko/ukko.js:33 +#: ../../../../templates/themes/ukko/ukko.js:45 +#: ../../../../templates/themes/ukko/ukko.js:56 +#: ../../../../templates/themes/ukko/ukko.js:68 +msgid "(show threads from this board)" +msgstr "(показати нитки цієї дошки)" + +#: ../../../../templates/themes/ukko/ukko.js:57 +#: ../../../../templates/themes/ukko/ukko.js:58 +#: ../../../../templates/themes/ukko/ukko.js:81 +msgid "No more threads to display" +msgstr "Було показано всі нитки" + +#: ../../../../templates/themes/ukko/ukko.js:79 +#: ../../../../templates/themes/ukko/ukko.js:80 +#: ../../../../templates/themes/ukko/ukko.js:103 +#: ../../../../js/infinite-scroll.js:48 +msgid "Loading..." +msgstr "Завантаження…" + +#: ../../../../js/download-original.js:32 +#: ../../../../js/download-original.js:33 +msgid "Save as original filename" +msgstr "Зберегти з початковим іменем файла" + +#: ../../../../js/ajax-post-controls.js:43 +msgid "Reported post(s)." +msgstr "Допис(и), про які повідомлено." + +#: ../../../../js/ajax-post-controls.js:53 +msgid "An unknown error occured!" +msgstr "Сталася невідома помилка!" + +#: ../../../../js/ajax-post-controls.js:60 +msgid "Something went wrong... An unknown error occured!" +msgstr "Щось пішло не так… Сталася невідома помилка!" + +#: ../../../../js/ajax-post-controls.js:68 +msgid "Working..." +msgstr "" + +#: ../../../../js/ajax.js:42 ../../../../js/ajax.js:45 +msgid "Posting... (#%)" +msgstr "Розміщення… (#%)" + +#: ../../../../js/ajax.js:104 ../../../../js/ajax.js:109 +msgid "Posted..." +msgstr "Розміщено…" + +#: ../../../../js/ajax.js:106 ../../../../js/ajax.js:111 +msgid "An unknown error occured when posting!" +msgstr "Під час розміщення сталася невідома помилка!" + +#: ../../../../js/ajax.js:130 ../../../../js/ajax.js:135 +msgid "Posting..." +msgstr "Розміщення…" + +#: ../../../../js/quick-reply.js:223 ../../../../js/quick-reply.js:224 +#: ../../../../js/quick-reply.js:225 +msgid "Upload URL" +msgstr "Адреса завантаження" + +#: ../../../../js/quick-reply.js:266 ../../../../js/quick-reply.js:267 +#: ../../../../js/quick-reply.js:268 +msgid "Spoiler Image" +msgstr "Спойлерне зображення" + +#: ../../../../js/quick-reply.js:277 ../../../../js/quick-reply.js:278 +#: ../../../../js/quick-reply.js:279 ../../../../js/quick-reply.js:281 +msgid "Comment" +msgstr "Коментувати" + +#: ../../../../js/quick-reply.js:285 ../../../../js/quick-reply.js:406 +#: ../../../../js/quick-reply.js:286 ../../../../js/quick-reply.js:407 +#: ../../../../js/quick-reply.js:287 ../../../../js/quick-reply.js:408 +#: ../../../../js/quick-reply.js:289 ../../../../js/quick-reply.js:410 +msgid "Quick Reply" +msgstr "Швидка відповідь" + +#: ../../../../js/watch.js:249 ../../../../js/watch.js:250 +#: ../../../../js/watch.js:288 ../../../../js/watch.js:289 +#: ../../../../js/watch.js:330 ../../../../js/watch.js:331 +#: ../../../../js/watch.js:361 ../../../../js/watch.js:362 +msgid "Stop watching this thread" +msgstr "Припинити стеження за ниткою" + +#: ../../../../js/watch.js:249 ../../../../js/watch.js:250 +#: ../../../../js/watch.js:288 ../../../../js/watch.js:289 +#: ../../../../js/watch.js:330 ../../../../js/watch.js:331 +#: ../../../../js/watch.js:361 ../../../../js/watch.js:362 +msgid "Watch this thread" +msgstr "Стежити за ниткою" + +#: ../../../../js/watch.js:260 ../../../../js/watch.js:261 +#: ../../../../js/watch.js:269 ../../../../js/watch.js:299 +#: ../../../../js/watch.js:300 ../../../../js/watch.js:308 +#: ../../../../js/watch.js:341 ../../../../js/watch.js:342 +#: ../../../../js/watch.js:350 ../../../../js/watch.js:372 +#: ../../../../js/watch.js:373 ../../../../js/watch.js:381 +msgid "Unpin this board" +msgstr "Відкріпити дошку" + +#: ../../../../js/watch.js:260 ../../../../js/watch.js:261 +#: ../../../../js/watch.js:269 ../../../../js/watch.js:299 +#: ../../../../js/watch.js:300 ../../../../js/watch.js:308 +#: ../../../../js/watch.js:341 ../../../../js/watch.js:342 +#: ../../../../js/watch.js:350 ../../../../js/watch.js:372 +#: ../../../../js/watch.js:373 ../../../../js/watch.js:381 +msgid "Pin this board" +msgstr "Прикріпити дошку" + +#: ../../../../js/watch.js:262 ../../../../js/watch.js:267 +#: ../../../../js/watch.js:268 ../../../../js/watch.js:301 +#: ../../../../js/watch.js:306 ../../../../js/watch.js:307 +#: ../../../../js/watch.js:343 ../../../../js/watch.js:348 +#: ../../../../js/watch.js:349 ../../../../js/watch.js:374 +#: ../../../../js/watch.js:379 ../../../../js/watch.js:380 +msgid "Stop watching this board" +msgstr "Не стежити за дошкою" + +#: ../../../../js/watch.js:262 ../../../../js/watch.js:267 +#: ../../../../js/watch.js:268 ../../../../js/watch.js:301 +#: ../../../../js/watch.js:306 ../../../../js/watch.js:307 +#: ../../../../js/watch.js:343 ../../../../js/watch.js:348 +#: ../../../../js/watch.js:349 ../../../../js/watch.js:374 +#: ../../../../js/watch.js:379 ../../../../js/watch.js:380 +msgid "Watch this board" +msgstr "Стежити за дошкою" + +#: ../../../../js/wpaint.js:113 +msgid "Click on any image on this site to load it into oekaki applet" +msgstr "" +"Клацніть по будь-якому зображенні на сторінці, щоб завантажити його до " +"аплету оекакі" + +#: ../../../../js/local-time.js:29 ../../../../templates/main.js:62 +msgid "Sunday" +msgstr "Неділя" + +#: ../../../../js/local-time.js:29 ../../../../templates/main.js:62 +msgid "Monday" +msgstr "Понеділок" + +#: ../../../../js/local-time.js:29 ../../../../templates/main.js:62 +msgid "Tuesday" +msgstr "Вівторок" + +#: ../../../../js/local-time.js:29 ../../../../templates/main.js:62 +msgid "Wednesday" +msgstr "Середа" + +#: ../../../../js/local-time.js:29 ../../../../templates/main.js:62 +msgid "Thursday" +msgstr "Четвер" + +#: ../../../../js/local-time.js:29 ../../../../templates/main.js:62 +msgid "Friday" +msgstr "Пʼятниця" + +#: ../../../../js/local-time.js:29 ../../../../templates/main.js:62 +msgid "Saturday" +msgstr "Субота" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "January" +msgstr "Січень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "February" +msgstr "Лютий" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "March" +msgstr "Березень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "April" +msgstr "Квітень" + +#: ../../../../js/local-time.js:31 ../../../../js/local-time.js:32 +#: ../../../../templates/main.js:64 ../../../../templates/main.js:65 +msgid "May" +msgstr "Травень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "June" +msgstr "Червень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "July" +msgstr "Липень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "August" +msgstr "Серпень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "September" +msgstr "Вересень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "October" +msgstr "Жовтень" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "November" +msgstr "Листопад" + +#: ../../../../js/local-time.js:31 ../../../../templates/main.js:64 +msgid "December" +msgstr "Грудень" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Jan" +msgstr "Січ" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Feb" +msgstr "Лют" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Mar" +msgstr "Бер" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Apr" +msgstr "Кві" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Jun" +msgstr "Чер" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Jul" +msgstr "Лип" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Aug" +msgstr "Сер" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Sep" +msgstr "Вер" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Oct" +msgstr "Жов" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Nov" +msgstr "Лис" + +#: ../../../../js/local-time.js:32 ../../../../templates/main.js:65 +msgid "Dec" +msgstr "Гру" + +#: ../../../../js/local-time.js:33 ../../../../templates/main.js:66 +msgid "AM" +msgstr "AM" + +#: ../../../../js/local-time.js:34 ../../../../templates/main.js:67 +msgid "PM" +msgstr "PM" + +#: ../../../../js/local-time.js:35 ../../../../templates/main.js:68 +msgid "am" +msgstr "am" + +#: ../../../../js/local-time.js:36 ../../../../templates/main.js:69 +msgid "pm" +msgstr "pm" + +#: ../../../../js/expand-video.js:45 ../../../../js/expand-video.js:48 +msgid "Your browser does not support HTML5 video." +msgstr "Ваш браузер не підтримує відео HTML5" + +#: ../../../../js/expand-video.js:189 ../../../../js/expand-video.js:192 +#: ../../../../js/expand-video.js:193 +msgid "[play once]" +msgstr "[відтворити раз]" + +#: ../../../../js/expand-video.js:190 ../../../../js/expand-video.js:193 +#: ../../../../js/expand-video.js:194 +msgid "[loop]" +msgstr "[зациклити]" + +#: ../../../../js/webm-settings.js:42 ../../../../js/webm-settings.js:45 +msgid "WebM Settings" +msgstr "Налаштування WebM" + +#: ../../../../js/webm-settings.js:44 ../../../../js/webm-settings.js:54 +msgid "Expand videos inline" +msgstr "Розгорнути відео на місці" + +#: ../../../../js/webm-settings.js:45 ../../../../js/webm-settings.js:55 +msgid "Play videos on hover" +msgstr "Відтворити відео в пливучому віконці" + +#: ../../../../js/webm-settings.js:46 ../../../../js/webm-settings.js:56 +msgid "Default volume" +msgstr "Гучність за промовчанням" + +#: ../../../../js/treeview.js:18 +msgid "Tree view" +msgstr "Перегляд деревом" + +#: ../../../../js/expand-all-images.js:32 +#: ../../../../js/expand-all-images.js:35 +msgid "Shrink all images" +msgstr "Позгортати всі зображення" + +#: ../../../../js/no-animated-gif.js:33 ../../../../js/no-animated-gif.js:37 +#: ../../../../js/no-animated-gif.js:40 +msgid "Animate GIFs" +msgstr "Увімкнути анімацію GIF" + +#: ../../../../js/no-animated-gif.js:42 ../../../../js/no-animated-gif.js:48 +#: ../../../../js/no-animated-gif.js:47 ../../../../js/no-animated-gif.js:57 +#: ../../../../js/no-animated-gif.js:62 ../../../../js/no-animated-gif.js:50 +#: ../../../../js/no-animated-gif.js:60 ../../../../js/no-animated-gif.js:65 +msgid "Unanimate GIFs" +msgstr "Вимкнути анімацію GIF" + +#: ../../../../js/webm-settings.js:41 +msgid "WebM" +msgstr "WebM" + +#: ../../../../js/live-index.js:25 ../../../../js/live-index.js:84 +msgid "No new posts." +msgstr "Немає нових дописів." + +#: ../../../../js/live-index.js:30 ../../../../js/live-index.js:73 +msgid "No new threads." +msgstr "Немає нових ниток." + +#: ../../../../js/live-index.js:72 +#, python-brace-format +msgid "There are {0} new threads." +msgstr "{0} нових ниток." + +#: ../../../../js/live-index.js:83 +#, python-brace-format +msgid "There are {0} new posts in this thread." +msgstr "{0} нових дописів у цій нитці." + +#: ../../../../js/options.js:106 +msgid "Options" +msgstr "Налаштування" + +#: ../../../../js/options/general.js:15 +msgid "General" +msgstr "Основні" + +#: ../../../../js/options/general.js:18 +msgid "Storage: " +msgstr "Сховище:" + +#: ../../../../js/options/general.js:21 +msgid "Export" +msgstr "Експортувати" + +#: ../../../../js/options/general.js:27 +msgid "Import" +msgstr "Імпортувати" + +#: ../../../../js/options/general.js:28 +msgid "Paste your storage data" +msgstr "Вставити дані зі сховища" + +#: ../../../../js/options/general.js:40 +msgid "Erase" +msgstr "Очистити" + +#: ../../../../js/options/general.js:41 +msgid "" +"Are you sure you want to erase your storage? This involves your hidden " +"threads, watched threads, post password and many more." +msgstr "" +"Ви впевнені, що хочете очистити своє сховище? Списки прихованих і " +"переглянутих ниток, пароль дописування та багато іншого буде анульовано." + +#: ../../../../js/options/user-css.js:14 +msgid "User CSS" +msgstr "Користувацький CSS" + +#: ../../../../js/options/user-css.js:23 +msgid "Update custom CSS" +msgstr "Оновити користувацький CSS" + +#: ../../../../js/options/user-css.js:45 +msgid "Enter here your own CSS rules..." +msgstr "Введіть власні правила CSS…" + +#: ../../../../js/options/user-css.js:46 +msgid "" +"If you want to make a redistributable style, be sure to\n" +" have a Yotsuba B theme selected." +msgstr "" +"Якщо хочете, щоб ваш стиль був переносним,\n" +"впевніться, що обрано тему Yotsuba B." + +#: ../../../../js/options/user-css.js:47 +msgid "You can include CSS files from remote servers, for example:" +msgstr "Ви можете включати файли CSS із віддалених серверів, наприклад:" + +#: ../../../../js/options/user-js.js:14 +msgid "User JS" +msgstr "Користувацький JS" + +#: ../../../../js/options/user-js.js:23 +msgid "Update custom Javascript" +msgstr "Оновити користувацький JS" + +#: ../../../../js/options/user-js.js:54 +msgid "Enter here your own Javascript code..." +msgstr "Введіть власний код Javascript…" + +#: ../../../../js/options/user-js.js:55 +msgid "" +"Have a backup of your storage somewhere, as messing here\n" +" may render you this website unusable." +msgstr "" +"Створіть десь в іншому місці резервну копію сховища,\n" +"бо може статись, що після змін тут ви будете не в змозі користуватися цим " +"сайтом." + +#: ../../../../js/options/user-js.js:56 +msgid "You can include JS files from remote servers, for example:" +msgstr "Ви можете включати файли CSS із віддалених серверів, наприклад:" + +#: ../../../../js/id_colors.js:6 ../../../../js/id_colors.js:12 +msgid "Color IDs" +msgstr "" + +#: ../../../../js/auto-reload.js:33 +msgid "Update" +msgstr "Оновлення" + +#: ../../../../js/mod/ban-list.js:29 +msgid "IP address" +msgstr "IP адреса" + +#: ../../../../js/mod/ban-list.js:42 +msgid "Seen" +msgstr "" + +#: ../../../../js/mod/ban-list.js:44 +msgid "Message for which user was banned is included" +msgstr "Повідомлення, за яке користувача заблоковано, додається" + +#: ../../../../js/mod/ban-list.js:45 +msgid "Message:" +msgstr "Повідомлення:" + +#: ../../../../js/mod/ban-list.js:53 +msgid "Board" +msgstr "Дошка" + +#: ../../../../js/mod/ban-list.js:55 +msgid "all" +msgstr "все" + +#: ../../../../js/mod/ban-list.js:57 +msgid "Set" +msgstr "Встановити" + +#: ../../../../js/mod/ban-list.js:58 +msgid " ago" +msgstr " тому" + +#: ../../../../js/mod/ban-list.js:61 +msgid "Expires" +msgstr "" +"Сплива\n" +"є" + +#: ../../../../js/mod/ban-list.js:62 +msgid "never" +msgstr "ніколи" + +#: ../../../../js/mod/ban-list.js:64 +msgid "in " +msgstr "в" + +#: ../../../../js/mod/ban-list.js:66 +msgid "Staff" +msgstr "Модератор" + +#: ../../../../js/mod/ban-list.js:73 +msgid "system" +msgstr "система" + +#: ../../../../js/auto-reload.js:33 +msgid "Auto" +msgstr "Авто" + +#: ../../../../js/auto-reload.js:141 +msgid "Updating..." +msgstr "Оновлення…" + +#: ../../../../js/auto-reload.js:183 +#, python-brace-format +msgid "Thread updated with {0} new post(s)" +msgstr "Нитку оновлено, зʼявилось {0} допис(ів)" + +#: ../../../../js/auto-reload.js:185 +msgid "No new posts found" +msgstr "Нових дописів немає" + +# Що мається на увазі під «pruned»? +#: ../../../../js/auto-reload.js:191 +msgid "Thread deleted or pruned" +msgstr "Нитку видалено або вилучено" + +#: ../../../../js/auto-reload.js:199 +msgid "Error: " +msgstr "Помилка:" + +#: ../../../../js/auto-reload.js:201 +msgid "Unknown error" +msgstr "Невідома помилка" + +#: ../../../../js/infinite-scroll.js:47 +msgid "Page" +msgstr "Сторінка" + +#: ../../../../js/infinite-scroll.js:82 +msgid "All" +msgstr "Всі" + +#: ../../../../templates/main.js:29 ../../../../templates/main.js:47 +msgid "second(s)" +msgstr "секунд(и)" + +#: ../../../../templates/main.js:31 ../../../../templates/main.js:49 +msgid "minute(s)" +msgstr "хвилин(и)" + +#: ../../../../templates/main.js:33 ../../../../templates/main.js:51 +msgid "hour(s)" +msgstr "годин(и)" + +#: ../../../../templates/main.js:35 ../../../../templates/main.js:53 +msgid "day(s)" +msgstr "день(дні)" + +#: ../../../../templates/main.js:37 ../../../../templates/main.js:55 +msgid "week(s)" +msgstr "тиждень(тижнів)" + +#: ../../../../templates/main.js:39 ../../../../templates/main.js:57 +msgid "year(s)" +msgstr "рік(років)" diff --git a/inc/locale/uk_UA/LC_MESSAGES/tinyboard.mo b/inc/locale/uk_UA/LC_MESSAGES/tinyboard.mo new file mode 100644 index 00000000..7454538d Binary files /dev/null and b/inc/locale/uk_UA/LC_MESSAGES/tinyboard.mo differ diff --git a/inc/locale/uk_UA/LC_MESSAGES/tinyboard.po b/inc/locale/uk_UA/LC_MESSAGES/tinyboard.po new file mode 100644 index 00000000..1360f044 --- /dev/null +++ b/inc/locale/uk_UA/LC_MESSAGES/tinyboard.po @@ -0,0 +1,3542 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-18 13:47+0200\n" +"PO-Revision-Date: 2016-06-19 17:31+0300\n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Last-Translator: kotobenko \n" +"Language-Team: \n" +"X-Generator: Poedit 1.8.8\n" + +#: ../../../../inc/functions.php:583 ../../../../inc/functions.php:600 +#: ../../../../inc/functions.php:591 ../../../../inc/functions.php:608 +#: ../../../../inc/functions.php:620 ../../../../inc/functions.php:637 +#: ../../../../inc/functions.php:623 ../../../../inc/functions.php:640 +#: ../../../../inc/functions.php:629 ../../../../inc/functions.php:646 +#: ../../../../inc/functions.php:643 ../../../../inc/functions.php:660 +#: ../../../../inc/functions.php:653 ../../../../inc/functions.php:670 +#: ../../../../inc/functions.php:655 ../../../../inc/functions.php:672 +#: ../../../../inc/functions.php:683 ../../../../inc/functions.php:700 +#: ../../../../inc/functions.php:701 ../../../../inc/functions.php:719 +msgid "second" +msgid_plural "seconds" +msgstr[0] "секунда" +msgstr[1] "секунди" +msgstr[2] "секунд" + +#. 60*60 = 3600 +#: ../../../../inc/functions.php:585 ../../../../inc/functions.php:602 +#: ../../../../inc/functions.php:593 ../../../../inc/functions.php:610 +#: ../../../../inc/functions.php:622 ../../../../inc/functions.php:639 +#: ../../../../inc/functions.php:625 ../../../../inc/functions.php:642 +#: ../../../../inc/functions.php:631 ../../../../inc/functions.php:648 +#: ../../../../inc/functions.php:645 ../../../../inc/functions.php:662 +#: ../../../../inc/functions.php:655 ../../../../inc/functions.php:672 +#: ../../../../inc/functions.php:657 ../../../../inc/functions.php:674 +#: ../../../../inc/functions.php:685 ../../../../inc/functions.php:702 +#: ../../../../inc/functions.php:703 ../../../../inc/functions.php:721 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "хвилина" +msgstr[1] "хвилини" +msgstr[2] "хвилин" + +#. 60*60*24 = 86400 +#: ../../../../inc/functions.php:587 ../../../../inc/functions.php:604 +#: ../../../../inc/functions.php:595 ../../../../inc/functions.php:612 +#: ../../../../inc/functions.php:624 ../../../../inc/functions.php:641 +#: ../../../../inc/functions.php:627 ../../../../inc/functions.php:644 +#: ../../../../inc/functions.php:633 ../../../../inc/functions.php:650 +#: ../../../../inc/functions.php:647 ../../../../inc/functions.php:664 +#: ../../../../inc/functions.php:657 ../../../../inc/functions.php:674 +#: ../../../../inc/functions.php:659 ../../../../inc/functions.php:676 +#: ../../../../inc/functions.php:687 ../../../../inc/functions.php:704 +#: ../../../../inc/functions.php:705 ../../../../inc/functions.php:723 +msgid "hour" +msgid_plural "hours" +msgstr[0] "година" +msgstr[1] "години" +msgstr[2] "годин" + +#. 60*60*24*7 = 604800 +#: ../../../../inc/functions.php:589 ../../../../inc/functions.php:606 +#: ../../../../inc/functions.php:597 ../../../../inc/functions.php:614 +#: ../../../../inc/functions.php:626 ../../../../inc/functions.php:643 +#: ../../../../inc/functions.php:629 ../../../../inc/functions.php:646 +#: ../../../../inc/functions.php:635 ../../../../inc/functions.php:652 +#: ../../../../inc/functions.php:649 ../../../../inc/functions.php:666 +#: ../../../../inc/functions.php:659 ../../../../inc/functions.php:676 +#: ../../../../inc/functions.php:661 ../../../../inc/functions.php:678 +#: ../../../../inc/functions.php:689 ../../../../inc/functions.php:706 +#: ../../../../inc/functions.php:707 ../../../../inc/functions.php:725 +msgid "day" +msgid_plural "days" +msgstr[0] "день" +msgstr[1] "дні" +msgstr[2] "днів" + +#. 60*60*24*365 = 31536000 +#: ../../../../inc/functions.php:591 ../../../../inc/functions.php:608 +#: ../../../../inc/functions.php:599 ../../../../inc/functions.php:616 +#: ../../../../inc/functions.php:628 ../../../../inc/functions.php:645 +#: ../../../../inc/functions.php:631 ../../../../inc/functions.php:648 +#: ../../../../inc/functions.php:637 ../../../../inc/functions.php:654 +#: ../../../../inc/functions.php:651 ../../../../inc/functions.php:668 +#: ../../../../inc/functions.php:661 ../../../../inc/functions.php:678 +#: ../../../../inc/functions.php:663 ../../../../inc/functions.php:680 +#: ../../../../inc/functions.php:691 ../../../../inc/functions.php:708 +#: ../../../../inc/functions.php:709 ../../../../inc/functions.php:727 +msgid "week" +msgid_plural "weeks" +msgstr[0] "тиждень" +msgstr[1] "тижні" +msgstr[2] "тижнів" + +#: ../../../../inc/functions.php:594 ../../../../inc/functions.php:611 +#: ../../../../inc/functions.php:602 ../../../../inc/functions.php:619 +#: ../../../../inc/functions.php:631 ../../../../inc/functions.php:648 +#: ../../../../inc/functions.php:634 ../../../../inc/functions.php:651 +#: ../../../../inc/functions.php:640 ../../../../inc/functions.php:657 +#: ../../../../inc/functions.php:654 ../../../../inc/functions.php:671 +#: ../../../../inc/functions.php:664 ../../../../inc/functions.php:681 +#: ../../../../inc/functions.php:666 ../../../../inc/functions.php:683 +#: ../../../../inc/functions.php:694 ../../../../inc/functions.php:711 +#: ../../../../inc/functions.php:729 +msgid "year" +msgid_plural "years" +msgstr[0] "рік" +msgstr[1] "роки" +msgstr[2] "років" + +#: ../../../../inc/functions.php:628 ../../../../inc/functions.php:670 +#: ../../../../inc/functions.php:699 ../../../../inc/functions.php:702 +#: ../../../../inc/functions.php:708 ../../../../inc/functions.php:722 +#: ../../../../inc/functions.php:732 ../../../../inc/functions.php:727 +#: ../../../../inc/functions.php:755 ../../../../inc/functions.php:774 +msgid "Banned!" +msgstr "Заблоковано!" + +#. There is no previous page. +#: ../../../../inc/functions.php:1125 ../../../../inc/functions.php:1139 +#: ../../../../inc/functions.php:1165 ../../../../inc/functions.php:1179 +#: ../../../../inc/functions.php:1168 ../../../../inc/functions.php:1182 +#: ../../../../inc/functions.php:1197 ../../../../inc/functions.php:1211 +#: ../../../../inc/functions.php:1200 ../../../../inc/functions.php:1214 +#: ../../../../inc/functions.php:1206 ../../../../inc/functions.php:1220 +#: ../../../../inc/functions.php:1234 ../../../../inc/functions.php:1230 +#: ../../../../inc/functions.php:1244 ../../../../inc/functions.php:1228 +#: ../../../../inc/functions.php:1242 ../../../../inc/functions.php:1256 +#: ../../../../inc/functions.php:1261 ../../../../inc/functions.php:1275 +msgid "Previous" +msgstr "Попередня" + +#. There is no next page. +#: ../../../../inc/functions.php:1144 ../../../../inc/functions.php:1153 +#: ../../../../inc/functions.php:1184 ../../../../inc/functions.php:1193 +#: ../../../../inc/functions.php:1187 ../../../../inc/functions.php:1196 +#: ../../../../inc/functions.php:1216 ../../../../inc/functions.php:1225 +#: ../../../../inc/functions.php:1219 ../../../../inc/functions.php:1228 +#: ../../../../inc/functions.php:1234 ../../../../inc/functions.php:1239 +#: ../../../../inc/functions.php:1248 ../../../../inc/functions.php:1249 +#: ../../../../inc/functions.php:1258 ../../../../inc/functions.php:1233 +#: ../../../../inc/functions.php:1242 ../../../../inc/functions.php:1261 +#: ../../../../inc/functions.php:1270 ../../../../inc/functions.php:1280 +#: ../../../../inc/functions.php:1289 +msgid "Next" +msgstr "Наступна" + +#: ../../../../inc/display.php:93 ../../../../inc/display.php:105 +#: ../../../../inc/display.php:108 ../../../../inc/display.php:112 +#: ../../../../inc/display.php:126 +msgid "Error" +msgstr "Помилка" + +#: ../../../../inc/display.php:94 ../../../../inc/display.php:106 +#: ../../../../inc/display.php:109 ../../../../inc/display.php:113 +#: ../../../../inc/display.php:127 +msgid "An error has occured." +msgstr "Сталася помилка." + +#: ../../../../inc/display.php:110 ../../../../inc/mod/pages.php:62 +#: ../../../../inc/mod/pages.php:60 ../../../../inc/display.php:122 +#: ../../../../inc/display.php:125 ../../../../inc/display.php:129 +#: ../../../../inc/display.php:143 +msgid "Login" +msgstr "Логін" + +#: ../../../../inc/display.php:229 ../../../../inc/display.php:241 +#: ../../../../inc/display.php:244 ../../../../inc/display.php:248 +#: ../../../../inc/display.php:262 +#, php-format +msgid "Post too long. Click here to view the full text." +msgstr "" +"Допис задовгий. Натисніть тут, щоб переглянути текст " +"повністю." + +#: ../../../../inc/display.php:368 ../../../../inc/display.php:473 +#: ../../../../inc/display.php:385 ../../../../inc/display.php:495 +#: ../../../../inc/display.php:388 ../../../../inc/display.php:498 +#: ../../../../inc/display.php:392 ../../../../inc/display.php:502 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:60 +msgid "Ban" +msgstr "Заблокувати" + +#: ../../../../inc/display.php:372 ../../../../inc/display.php:477 +#: ../../../../inc/display.php:389 ../../../../inc/display.php:499 +#: ../../../../inc/display.php:392 ../../../../inc/display.php:502 +#: ../../../../inc/display.php:396 ../../../../inc/display.php:506 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:74 +msgid "Ban & Delete" +msgstr "Заблокувати й видалити" + +#. line 4 +#: ../../../../inc/display.php:376 ../../../../inc/display.php:481 +#: ../../../../inc/display.php:393 ../../../../inc/display.php:503 +#: ../../../../inc/display.php:396 ../../../../inc/display.php:506 +#: ../../../../inc/display.php:400 ../../../../inc/display.php:510 +#: ../../../../templates/cache/b9/2b/ba2b45df5e1d76f6cdfb98a47468df19a1ffc1c2af2dc1792eb75eeb0791.php:29 +msgid "Delete file" +msgstr "Видалити файл" + +#: ../../../../inc/display.php:376 ../../../../inc/display.php:481 +#: ../../../../inc/display.php:393 ../../../../inc/display.php:503 +#: ../../../../inc/display.php:396 ../../../../inc/display.php:506 +#: ../../../../inc/display.php:400 ../../../../inc/display.php:510 +#: ../../../../templates/cache/b9/2b/ba2b45df5e1d76f6cdfb98a47468df19a1ffc1c2af2dc1792eb75eeb0791.php:29 +msgid "Are you sure you want to delete this file?" +msgstr "Ви впевнені, що хочете видалити цей файл?" + +#: ../../../../inc/display.php:380 ../../../../inc/display.php:485 +#: ../../../../inc/display.php:397 ../../../../inc/display.php:507 +#: ../../../../inc/display.php:400 ../../../../inc/display.php:510 +#: ../../../../inc/display.php:404 ../../../../inc/display.php:514 +msgid "Spoiler File" +msgstr "Заховати файл у спойлер" + +#: ../../../../inc/display.php:380 ../../../../inc/display.php:485 +#: ../../../../inc/display.php:397 ../../../../inc/display.php:507 +#: ../../../../inc/display.php:400 ../../../../inc/display.php:510 +#: ../../../../inc/display.php:404 ../../../../inc/display.php:514 +#: ../../../../templates/cache/b9/2b/ba2b45df5e1d76f6cdfb98a47468df19a1ffc1c2af2dc1792eb75eeb0791.php:36 +msgid "Are you sure you want to spoiler this file?" +msgstr "Ви впевнені, що хочете заховати цей файл у спойлер?" + +# ОП з усіма дописами чи тільки один допис? +#: ../../../../inc/display.php:384 ../../../../inc/display.php:401 +#: ../../../../inc/display.php:404 ../../../../inc/display.php:408 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:193 +msgid "Move reply to another board" +msgstr "Перемістити відповідь до іншої дошки" + +#: ../../../../inc/display.php:388 ../../../../inc/display.php:512 +#: ../../../../inc/mod/pages.php:1425 ../../../../inc/mod/pages.php:1494 +#: ../../../../inc/display.php:405 ../../../../inc/display.php:534 +#: ../../../../inc/display.php:408 ../../../../inc/display.php:537 +#: ../../../../inc/display.php:412 ../../../../inc/display.php:541 +#: ../../../../inc/mod/pages.php:1508 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:208 +#: ../../../../inc/mod/pages.php:1509 ../../../../inc/mod/pages.php:1506 +#: ../../../../inc/mod/pages.php:1511 +msgid "Edit post" +msgstr "Редагувати допис" + +#. line 5 +#. line 6 +#: ../../../../inc/display.php:461 +#: ../../../../templates/cache/82/40/4c4a4b82f787181e6500ce83494d.php:33 +#: ../../../../inc/display.php:483 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:36 +#: ../../../../inc/display.php:486 ../../../../inc/display.php:490 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:38 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:36 +msgid "Delete" +msgstr "Видалити" + +#: ../../../../inc/display.php:461 ../../../../inc/display.php:483 +#: ../../../../inc/display.php:486 ../../../../inc/display.php:490 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:36 +msgid "Are you sure you want to delete this?" +msgstr "Ви впевнені, що хочете видалити?" + +#: ../../../../inc/display.php:465 ../../../../inc/display.php:487 +#: ../../../../inc/display.php:490 ../../../../inc/display.php:494 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:44 +msgid "Delete all posts by IP" +msgstr "Видалити всі дописи цього IP" + +#: ../../../../inc/display.php:465 ../../../../inc/display.php:487 +#: ../../../../inc/display.php:490 ../../../../inc/display.php:494 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:44 +msgid "Are you sure you want to delete all posts by this IP address?" +msgstr "Ви впевнені, що хочете видалити всі дописи цього IP?" + +#: ../../../../inc/display.php:469 ../../../../inc/display.php:491 +#: ../../../../inc/display.php:494 ../../../../inc/display.php:498 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:52 +msgid "Delete all posts by IP across all boards" +msgstr "Видалити всі дописи цього IP на всіх дошках" + +#: ../../../../inc/display.php:469 ../../../../inc/display.php:491 +#: ../../../../inc/display.php:494 ../../../../inc/display.php:498 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:52 +msgid "" +"Are you sure you want to delete all posts by this IP address, across all " +"boards?" +msgstr "Ви впенені, що хочете видалити всі дописи цього IP на всіх дошках?" + +#: ../../../../inc/display.php:490 ../../../../inc/display.php:512 +#: ../../../../inc/display.php:515 ../../../../inc/display.php:519 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:96 +msgid "Make thread not sticky" +msgstr "Відкріпити нитку" + +#: ../../../../inc/display.php:492 ../../../../inc/display.php:514 +#: ../../../../inc/display.php:517 ../../../../inc/display.php:521 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:106 +msgid "Make thread sticky" +msgstr "Закріпити нитку" + +#: ../../../../inc/display.php:496 ../../../../inc/display.php:518 +#: ../../../../inc/display.php:521 ../../../../inc/display.php:525 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:122 +msgid "Allow thread to be bumped" +msgstr "Увімкнути цій нитці підіймання" + +#: ../../../../inc/display.php:498 ../../../../inc/display.php:520 +#: ../../../../inc/display.php:523 ../../../../inc/display.php:527 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:132 +msgid "Prevent thread from being bumped" +msgstr "Вимкнути цій нитці підіймання" + +#: ../../../../inc/display.php:503 ../../../../inc/display.php:525 +#: ../../../../inc/display.php:528 ../../../../inc/display.php:532 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:148 +msgid "Unlock thread" +msgstr "Відкрити нитку" + +#: ../../../../inc/display.php:505 ../../../../inc/display.php:527 +#: ../../../../inc/display.php:530 ../../../../inc/display.php:534 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:158 +msgid "Lock thread" +msgstr "Закрити нитку" + +#: ../../../../inc/display.php:508 ../../../../inc/display.php:530 +#: ../../../../inc/display.php:533 ../../../../inc/display.php:537 +#: ../../../../templates/cache/59/eb/57ea544c6cb07c523441e4872c03216560589e8425c0df3a895c17253c4c.php:181 +msgid "Move thread to another board" +msgstr "Перемістити нитку на іншу дошку" + +#. How long before Tinyboard forgets about a mute? +#. 2 weeks +#. If you want to alter the algorithm a bit. Default value is 2. +#. (n^x where x is the number of previous mutes) +#: ../../../../inc/config.php:346 ../../../../inc/config.php:473 +#: ../../../../inc/config.php:474 ../../../../inc/config.php:475 +#: ../../../../inc/config.php:476 ../../../../inc/config.php:479 +#: ../../../../inc/config.php:482 +msgid "You have been muted for unoriginal content." +msgstr "Вас позбавлено права дописувати за неоригінальний контент." + +#. The names on the post buttons. (On most imageboards, these are both just +#. "Post"). +#. The names on the post buttons. (On most imageboards, these are both just "Post"). +#: ../../../../inc/config.php:677 ../../../../inc/config.php:781 +#: ../../../../inc/config.php:772 ../../../../inc/config.php:774 +#: ../../../../inc/config.php:776 ../../../../inc/config.php:792 +#: ../../../../inc/config.php:803 ../../../../inc/config.php:829 +#: ../../../../inc/config.php:832 ../../../../inc/config.php:835 +msgid "New Topic" +msgstr "Нова тема" + +#: ../../../../inc/config.php:678 ../../../../inc/config.php:782 +#: ../../../../inc/config.php:773 ../../../../inc/config.php:775 +#: ../../../../inc/config.php:777 ../../../../inc/config.php:793 +#: ../../../../inc/config.php:804 ../../../../inc/config.php:830 +#: ../../../../inc/config.php:833 ../../../../inc/config.php:836 +msgid "New Reply" +msgstr "Нова відповідь" + +#. Additional lines added to the footer of all pages. +#: ../../../../inc/config.php:689 ../../../../inc/config.php:793 +#: ../../../../inc/config.php:784 ../../../../inc/config.php:786 +#: ../../../../inc/config.php:788 ../../../../inc/config.php:804 +#: ../../../../inc/config.php:815 ../../../../inc/config.php:841 +#: ../../../../inc/config.php:844 ../../../../inc/config.php:847 +msgid "" +"All trademarks, copyrights, comments, and images on this page are owned by " +"and are the responsibility of their respective parties." +msgstr "" + +#. * ==================== +#. * Error messages +#. * ==================== +#. Error messages +#: ../../../../inc/config.php:866 +msgid "Lurk some more before posting." +msgstr "" +"Вам не завадило б почитати Українську Драматику, перш ніж щось сюди писати." + +#. * ==================== +#. * Error messages +#. * ==================== +#. Error messages +#. +#. * ==================== +#. * Error messages +#. * ==================== +#. +#. Error messages +#: ../../../../inc/config.php:867 ../../../../inc/config.php:972 +#: ../../../../inc/config.php:963 ../../../../inc/config.php:965 +#: ../../../../inc/config.php:967 ../../../../inc/config.php:983 +#: ../../../../inc/config.php:991 ../../../../inc/config.php:1017 +#: ../../../../inc/config.php:1020 ../../../../inc/config.php:1023 +msgid "You look like a bot." +msgstr "Ви поводитесь як бот." + +#: ../../../../inc/config.php:868 ../../../../inc/config.php:973 +#: ../../../../inc/config.php:964 ../../../../inc/config.php:966 +#: ../../../../inc/config.php:968 ../../../../inc/config.php:984 +#: ../../../../inc/config.php:992 ../../../../inc/config.php:1018 +#: ../../../../inc/config.php:1021 ../../../../inc/config.php:1024 +msgid "Your browser sent an invalid or no HTTP referer." +msgstr "Ваш браузер не надіслав HTTP реферера або надіслав хибний" + +#: ../../../../inc/config.php:869 ../../../../inc/config.php:974 +#: ../../../../inc/config.php:965 ../../../../inc/config.php:967 +#: ../../../../inc/config.php:969 ../../../../inc/config.php:985 +#: ../../../../inc/config.php:993 ../../../../inc/config.php:1019 +#: ../../../../inc/config.php:1022 ../../../../inc/config.php:1025 +#, php-format +msgid "The %s field was too long." +msgstr "Поле %s занадто довге." + +#: ../../../../inc/config.php:870 ../../../../inc/config.php:975 +#: ../../../../inc/config.php:966 ../../../../inc/config.php:968 +#: ../../../../inc/config.php:970 ../../../../inc/config.php:986 +#: ../../../../inc/config.php:994 ../../../../inc/config.php:1020 +#: ../../../../inc/config.php:1023 ../../../../inc/config.php:1026 +msgid "The body was too long." +msgstr "Вміст допису занадто довге." + +#: ../../../../inc/config.php:871 ../../../../inc/config.php:976 +#: ../../../../inc/config.php:967 ../../../../inc/config.php:969 +#: ../../../../inc/config.php:971 ../../../../inc/config.php:987 +#: ../../../../inc/config.php:995 ../../../../inc/config.php:1021 +#: ../../../../inc/config.php:1024 ../../../../inc/config.php:1027 +msgid "The body was too short or empty." +msgstr "Вміст допису занадто короткий або пустий." + +#: ../../../../inc/config.php:872 ../../../../inc/config.php:977 +#: ../../../../inc/config.php:968 ../../../../inc/config.php:970 +#: ../../../../inc/config.php:972 ../../../../inc/config.php:988 +#: ../../../../inc/config.php:996 ../../../../inc/config.php:1022 +#: ../../../../inc/config.php:1025 ../../../../inc/config.php:1028 +msgid "You must upload an image." +msgstr "Допис повинен містити зображення." + +#: ../../../../inc/config.php:873 ../../../../inc/config.php:978 +#: ../../../../inc/config.php:969 ../../../../inc/config.php:971 +#: ../../../../inc/config.php:973 ../../../../inc/config.php:989 +#: ../../../../inc/config.php:998 ../../../../inc/config.php:1024 +#: ../../../../inc/config.php:1027 ../../../../inc/config.php:1030 +msgid "The server failed to handle your upload." +msgstr "Не вдалося завантажити файл на сервер." + +#: ../../../../inc/config.php:874 ../../../../inc/config.php:979 +#: ../../../../inc/config.php:970 ../../../../inc/config.php:972 +#: ../../../../inc/config.php:974 ../../../../inc/config.php:990 +#: ../../../../inc/config.php:999 ../../../../inc/config.php:1025 +#: ../../../../inc/config.php:1028 ../../../../inc/config.php:1031 +msgid "Unsupported image format." +msgstr "Формат зображення не підтримується." + +#: ../../../../inc/config.php:875 ../../../../inc/config.php:980 +#: ../../../../inc/config.php:971 ../../../../inc/config.php:973 +#: ../../../../inc/config.php:975 ../../../../inc/config.php:991 +#: ../../../../inc/config.php:1000 ../../../../inc/config.php:1026 +#: ../../../../inc/config.php:1029 ../../../../inc/config.php:1032 +msgid "Invalid board!" +msgstr "Неправильна дошка!" + +#: ../../../../inc/config.php:876 ../../../../inc/config.php:981 +#: ../../../../inc/config.php:972 ../../../../inc/config.php:974 +#: ../../../../inc/config.php:976 ../../../../inc/config.php:992 +#: ../../../../inc/config.php:1001 ../../../../inc/config.php:1027 +#: ../../../../inc/config.php:1030 ../../../../inc/config.php:1033 +msgid "Thread specified does not exist." +msgstr "Вказана нитка не існує." + +#: ../../../../inc/config.php:877 ../../../../inc/config.php:982 +#: ../../../../inc/config.php:973 ../../../../inc/config.php:975 +#: ../../../../inc/config.php:977 ../../../../inc/config.php:993 +#: ../../../../inc/config.php:1002 ../../../../inc/config.php:1028 +#: ../../../../inc/config.php:1031 ../../../../inc/config.php:1034 +msgid "Thread locked. You may not reply at this time." +msgstr "Нитку закрито. Ви більше не можете сюди дописати." + +#: ../../../../inc/config.php:878 ../../../../inc/config.php:983 +#: ../../../../inc/config.php:974 ../../../../inc/config.php:976 +#: ../../../../inc/config.php:978 ../../../../inc/config.php:994 +#: ../../../../inc/config.php:1003 ../../../../inc/config.php:1029 +#: ../../../../inc/config.php:1032 ../../../../inc/config.php:1035 +msgid "Thread has reached its maximum reply limit." +msgstr "Кількість дописів у нитці досягла максимально припустимої." + +#: ../../../../inc/config.php:879 ../../../../inc/config.php:984 +#: ../../../../inc/config.php:975 ../../../../inc/config.php:977 +#: ../../../../inc/config.php:979 ../../../../inc/config.php:995 +#: ../../../../inc/config.php:1004 ../../../../inc/config.php:1030 +#: ../../../../inc/config.php:1033 ../../../../inc/config.php:1036 +msgid "Thread has reached its maximum image limit." +msgstr "Кількість зображень у нитці досягла максимально припустимої." + +#: ../../../../inc/config.php:880 ../../../../inc/config.php:985 +#: ../../../../inc/config.php:976 ../../../../inc/config.php:978 +#: ../../../../inc/config.php:980 ../../../../inc/config.php:996 +#: ../../../../inc/config.php:1005 ../../../../inc/config.php:1031 +#: ../../../../inc/config.php:1034 ../../../../inc/config.php:1037 +msgid "You didn't make a post." +msgstr "Ви не розмістили допис." + +#: ../../../../inc/config.php:881 ../../../../inc/config.php:986 +#: ../../../../inc/config.php:977 ../../../../inc/config.php:979 +#: ../../../../inc/config.php:981 ../../../../inc/config.php:997 +#: ../../../../inc/config.php:1006 ../../../../inc/config.php:1032 +#: ../../../../inc/config.php:1035 ../../../../inc/config.php:1038 +msgid "Flood detected; Post discarded." +msgstr "Виявлено флуд — опис відхилено." + +#: ../../../../inc/config.php:882 ../../../../inc/config.php:987 +#: ../../../../inc/config.php:978 ../../../../inc/config.php:980 +#: ../../../../inc/config.php:982 ../../../../inc/config.php:998 +#: ../../../../inc/config.php:1007 ../../../../inc/config.php:1033 +#: ../../../../inc/config.php:1036 ../../../../inc/config.php:1039 +msgid "Your request looks automated; Post discarded." +msgstr "Ваш запит схожий на автоматизований— допис відхилено." + +#: ../../../../inc/config.php:883 ../../../../inc/config.php:988 +#: ../../../../inc/config.php:979 ../../../../inc/config.php:981 +#: ../../../../inc/config.php:983 ../../../../inc/config.php:999 +#: ../../../../inc/config.php:1008 ../../../../inc/config.php:1034 +#: ../../../../inc/config.php:1037 ../../../../inc/config.php:1040 +msgid "Unoriginal content!" +msgstr "Неоригінальний контент!" + +#: ../../../../inc/config.php:884 ../../../../inc/config.php:989 +#: ../../../../inc/config.php:980 ../../../../inc/config.php:982 +#: ../../../../inc/config.php:984 ../../../../inc/config.php:1000 +#: ../../../../inc/config.php:1009 ../../../../inc/config.php:1035 +#: ../../../../inc/config.php:1038 ../../../../inc/config.php:1041 +#, php-format +msgid "Unoriginal content! You have been muted for %d seconds." +msgstr "" +"Неоригінальний контент! Вас позбавлено можливості дописувати на %d секунд." + +#: ../../../../inc/config.php:885 ../../../../inc/config.php:990 +#: ../../../../inc/config.php:981 ../../../../inc/config.php:983 +#: ../../../../inc/config.php:985 ../../../../inc/config.php:1001 +#: ../../../../inc/config.php:1010 ../../../../inc/config.php:1036 +#: ../../../../inc/config.php:1039 ../../../../inc/config.php:1042 +#, php-format +msgid "You are muted! Expires in %d seconds." +msgstr "Вас позбавлено можливості дописувати! Буде повернено через %d секунд." + +#: ../../../../inc/config.php:886 ../../../../inc/config.php:991 +#: ../../../../inc/config.php:982 ../../../../inc/config.php:984 +#: ../../../../inc/config.php:986 ../../../../inc/config.php:1002 +#: ../../../../inc/config.php:1011 ../../../../inc/config.php:1037 +#: ../../../../inc/config.php:1040 ../../../../inc/config.php:1043 +#, php-format +msgid "Your IP address is listed in %s." +msgstr "Вашу IP адресу знайдено в списку %s." + +#: ../../../../inc/config.php:887 ../../../../inc/config.php:992 +#: ../../../../inc/config.php:983 ../../../../inc/config.php:985 +#: ../../../../inc/config.php:987 ../../../../inc/config.php:1003 +#: ../../../../inc/config.php:1012 ../../../../inc/config.php:1038 +#: ../../../../inc/config.php:1041 ../../../../inc/config.php:1044 +msgid "Too many links; flood detected." +msgstr "Забагато посилань — виявлено флуд." + +#: ../../../../inc/config.php:888 ../../../../inc/config.php:993 +#: ../../../../inc/config.php:984 ../../../../inc/config.php:986 +#: ../../../../inc/config.php:988 ../../../../inc/config.php:1004 +#: ../../../../inc/config.php:1013 ../../../../inc/config.php:1039 +#: ../../../../inc/config.php:1042 ../../../../inc/config.php:1045 +msgid "Too many cites; post discarded." +msgstr "Забагато цитування — допис відхилено." + +#: ../../../../inc/config.php:889 ../../../../inc/config.php:994 +#: ../../../../inc/config.php:985 ../../../../inc/config.php:987 +#: ../../../../inc/config.php:989 ../../../../inc/config.php:1005 +#: ../../../../inc/config.php:1014 ../../../../inc/config.php:1040 +#: ../../../../inc/config.php:1043 ../../../../inc/config.php:1046 +msgid "Too many cross-board links; post discarded." +msgstr "Забагато міждошкових посилань — допис відхилено." + +#: ../../../../inc/config.php:890 ../../../../inc/config.php:995 +#: ../../../../inc/config.php:986 ../../../../inc/config.php:988 +#: ../../../../inc/config.php:990 ../../../../inc/config.php:1006 +#: ../../../../inc/config.php:1015 ../../../../inc/config.php:1041 +#: ../../../../inc/config.php:1044 ../../../../inc/config.php:1047 +msgid "You didn't select anything to delete." +msgstr "Ви не вибрали жодного допису, щоб видалити." + +#: ../../../../inc/config.php:891 ../../../../inc/config.php:996 +#: ../../../../inc/config.php:987 ../../../../inc/config.php:989 +#: ../../../../inc/config.php:991 ../../../../inc/config.php:1007 +#: ../../../../inc/config.php:1016 ../../../../inc/config.php:1042 +#: ../../../../inc/config.php:1045 ../../../../inc/config.php:1048 +msgid "You didn't select anything to report." +msgstr "Ви не вибрали жодного допису, щоб поскаржитися." + +#: ../../../../inc/config.php:892 ../../../../inc/config.php:997 +#: ../../../../inc/config.php:988 ../../../../inc/config.php:990 +#: ../../../../inc/config.php:992 ../../../../inc/config.php:1008 +#: ../../../../inc/config.php:1017 ../../../../inc/config.php:1043 +#: ../../../../inc/config.php:1046 ../../../../inc/config.php:1049 +msgid "You can't report that many posts at once." +msgstr "Ви не можете поскаржитися на таку кількість дописів відразу." + +#: ../../../../inc/config.php:893 ../../../../inc/config.php:998 +#: ../../../../inc/config.php:989 ../../../../inc/config.php:991 +#: ../../../../inc/config.php:993 ../../../../inc/config.php:1009 +#: ../../../../inc/config.php:1018 ../../../../inc/config.php:1044 +#: ../../../../inc/config.php:1047 ../../../../inc/config.php:1050 +msgid "Wrong password…" +msgstr "Неправильний пароль…" + +#: ../../../../inc/config.php:894 ../../../../inc/config.php:999 +#: ../../../../inc/config.php:990 ../../../../inc/config.php:992 +#: ../../../../inc/config.php:994 ../../../../inc/config.php:1010 +#: ../../../../inc/config.php:1019 ../../../../inc/config.php:1045 +#: ../../../../inc/config.php:1048 ../../../../inc/config.php:1051 +msgid "Invalid image." +msgstr "Неправильне зображення." + +#: ../../../../inc/config.php:895 ../../../../inc/config.php:1000 +#: ../../../../inc/config.php:991 ../../../../inc/config.php:993 +#: ../../../../inc/config.php:995 ../../../../inc/config.php:1011 +#: ../../../../inc/config.php:1020 ../../../../inc/config.php:1046 +#: ../../../../inc/config.php:1049 ../../../../inc/config.php:1052 +msgid "Unknown file extension." +msgstr "Невідоме розширення файла." + +#: ../../../../inc/config.php:896 ../../../../inc/config.php:1001 +#: ../../../../inc/config.php:992 ../../../../inc/config.php:994 +#: ../../../../inc/config.php:996 ../../../../inc/config.php:1012 +#: ../../../../inc/config.php:1021 ../../../../inc/config.php:1047 +#: ../../../../inc/config.php:1050 ../../../../inc/config.php:1053 +msgid "Maximum file size: %maxsz% bytes
Your file's size: %filesz% bytes" +msgstr "" +"Максимальний розмір файла: %maxsz% байтів
Розмір вашого файла: %filesz% " +"байтів" + +#: ../../../../inc/config.php:897 ../../../../inc/config.php:1002 +#: ../../../../inc/config.php:993 ../../../../inc/config.php:995 +#: ../../../../inc/config.php:997 ../../../../inc/config.php:1013 +#: ../../../../inc/config.php:1022 ../../../../inc/config.php:1048 +#: ../../../../inc/config.php:1051 ../../../../inc/config.php:1054 +msgid "The file was too big." +msgstr "Файл завеликий." + +#: ../../../../inc/config.php:898 ../../../../inc/config.php:1003 +#: ../../../../inc/config.php:994 ../../../../inc/config.php:996 +#: ../../../../inc/config.php:998 ../../../../inc/config.php:1014 +#: ../../../../inc/config.php:1023 ../../../../inc/config.php:1053 +#: ../../../../inc/config.php:1057 ../../../../inc/config.php:1060 +#, php-format +msgid "That file already exists!" +msgstr "Файл уже існує!" + +#: ../../../../inc/config.php:899 ../../../../inc/config.php:1004 +#: ../../../../inc/config.php:995 ../../../../inc/config.php:997 +#: ../../../../inc/config.php:999 ../../../../inc/config.php:1015 +#: ../../../../inc/config.php:1024 ../../../../inc/config.php:1054 +#: ../../../../inc/config.php:1058 ../../../../inc/config.php:1061 +#, php-format +msgid "That file already exists in this thread!" +msgstr "Файл уже існує у цій нитці!" + +#: ../../../../inc/config.php:900 ../../../../inc/config.php:1005 +#: ../../../../inc/config.php:996 ../../../../inc/config.php:998 +#: ../../../../inc/config.php:1000 ../../../../inc/config.php:1016 +#: ../../../../inc/config.php:1025 ../../../../inc/config.php:1055 +#: ../../../../inc/config.php:1059 ../../../../inc/config.php:1062 +#, php-format +msgid "You'll have to wait another %s before deleting that." +msgstr "Почекайте ще %s перед видаленням." + +#: ../../../../inc/config.php:901 ../../../../inc/config.php:1006 +#: ../../../../inc/config.php:997 ../../../../inc/config.php:999 +#: ../../../../inc/config.php:1001 ../../../../inc/config.php:1017 +#: ../../../../inc/config.php:1026 ../../../../inc/config.php:1056 +#: ../../../../inc/config.php:1060 ../../../../inc/config.php:1063 +msgid "MIME type detection XSS exploit (IE) detected; post discarded." +msgstr "Виявлено XSS-експлойт визначення MIME-типу — допис відхилено." + +#: ../../../../inc/config.php:902 ../../../../inc/config.php:1007 +#: ../../../../inc/config.php:998 ../../../../inc/config.php:1000 +#: ../../../../inc/config.php:1002 ../../../../inc/config.php:1018 +#: ../../../../inc/config.php:1027 ../../../../inc/config.php:1057 +#: ../../../../inc/config.php:1061 ../../../../inc/config.php:1064 +msgid "Couldn't make sense of the URL of the video you tried to embed." +msgstr "Не вдалося розпізнати адресу відео, яке ви намагалися вбудувати." + +#: ../../../../inc/config.php:903 ../../../../inc/config.php:1008 +#: ../../../../inc/config.php:999 ../../../../inc/config.php:1001 +#: ../../../../inc/config.php:1003 ../../../../inc/config.php:1019 +#: ../../../../inc/config.php:1028 ../../../../inc/config.php:1058 +#: ../../../../inc/config.php:1062 ../../../../inc/config.php:1065 +msgid "You seem to have mistyped the verification." +msgstr "Здається, ви не пройшли підтвердження." + +#. Moderator errors +#: ../../../../inc/config.php:906 ../../../../inc/config.php:1011 +#: ../../../../inc/config.php:1002 ../../../../inc/config.php:1004 +#: ../../../../inc/config.php:1006 ../../../../inc/config.php:1022 +#: ../../../../inc/config.php:1031 ../../../../inc/config.php:1062 +#: ../../../../inc/config.php:1066 ../../../../inc/config.php:1069 +#, php-format +msgid "" +"You are only allowed to unban %s users at a time. You tried to unban %u " +"users." +msgstr "" +"Дозволяється розблоковувати лише %s користувачів за раз. Ви намагались " +"розблокувати %u користувачів." + +#: ../../../../inc/config.php:907 ../../../../inc/config.php:1012 +#: ../../../../inc/config.php:1003 ../../../../inc/config.php:1005 +#: ../../../../inc/config.php:1007 ../../../../inc/config.php:1023 +#: ../../../../inc/config.php:1032 ../../../../inc/config.php:1063 +#: ../../../../inc/config.php:1067 ../../../../inc/config.php:1070 +msgid "Invalid username and/or password." +msgstr "Неправильне імʼя користувача і/або пароль." + +#: ../../../../inc/config.php:908 ../../../../inc/config.php:1013 +#: ../../../../inc/config.php:1004 ../../../../inc/config.php:1006 +#: ../../../../inc/config.php:1008 ../../../../inc/config.php:1024 +#: ../../../../inc/config.php:1033 ../../../../inc/config.php:1064 +#: ../../../../inc/config.php:1068 ../../../../inc/config.php:1071 +msgid "You are not a mod…" +msgstr "Ви не модератор…" + +#: ../../../../inc/config.php:909 ../../../../inc/config.php:1014 +#: ../../../../inc/config.php:1005 ../../../../inc/config.php:1007 +#: ../../../../inc/config.php:1009 ../../../../inc/config.php:1025 +#: ../../../../inc/config.php:1034 ../../../../inc/config.php:1065 +#: ../../../../inc/config.php:1069 ../../../../inc/config.php:1072 +msgid "" +"Invalid username and/or password. Your user may have been deleted or changed." +msgstr "" +"Неправильне імʼя користувача і/або пароль. Можливо, ваш обліковий запис " +"видалено або змінено." + +#: ../../../../inc/config.php:910 ../../../../inc/config.php:1015 +#: ../../../../inc/config.php:1006 ../../../../inc/config.php:1008 +#: ../../../../inc/config.php:1010 ../../../../inc/config.php:1026 +#: ../../../../inc/config.php:1035 ../../../../inc/config.php:1066 +#: ../../../../inc/config.php:1070 ../../../../inc/config.php:1073 +msgid "Invalid/malformed cookies." +msgstr "Неправильні/неправильно сформовані куки." + +#: ../../../../inc/config.php:911 ../../../../inc/config.php:1016 +#: ../../../../inc/config.php:1007 ../../../../inc/config.php:1009 +#: ../../../../inc/config.php:1011 ../../../../inc/config.php:1027 +#: ../../../../inc/config.php:1036 ../../../../inc/config.php:1067 +#: ../../../../inc/config.php:1071 ../../../../inc/config.php:1074 +msgid "Your browser didn't submit an input when it should have." +msgstr "Ваш браузер не підтвердив введення тоді, коли потрібно." + +#: ../../../../inc/config.php:912 ../../../../inc/config.php:1017 +#: ../../../../inc/config.php:1008 ../../../../inc/config.php:1010 +#: ../../../../inc/config.php:1012 ../../../../inc/config.php:1028 +#: ../../../../inc/config.php:1037 ../../../../inc/config.php:1068 +#: ../../../../inc/config.php:1072 ../../../../inc/config.php:1075 +#, php-format +msgid "The %s field is required." +msgstr "Поле %s обовʼязкове." + +#: ../../../../inc/config.php:913 ../../../../inc/config.php:1018 +#: ../../../../inc/config.php:1009 ../../../../inc/config.php:1011 +#: ../../../../inc/config.php:1013 ../../../../inc/config.php:1029 +#: ../../../../inc/config.php:1038 ../../../../inc/config.php:1069 +#: ../../../../inc/config.php:1073 ../../../../inc/config.php:1076 +#, php-format +msgid "The %s field was invalid." +msgstr "Поле %s заповнено неправильно." + +#: ../../../../inc/config.php:914 ../../../../inc/config.php:1019 +#: ../../../../inc/config.php:1010 ../../../../inc/config.php:1012 +#: ../../../../inc/config.php:1014 ../../../../inc/config.php:1030 +#: ../../../../inc/config.php:1039 ../../../../inc/config.php:1070 +#: ../../../../inc/config.php:1074 ../../../../inc/config.php:1077 +#, php-format +msgid "There is already a %s board." +msgstr "Дошка %s уже існує." + +#: ../../../../inc/config.php:915 ../../../../inc/config.php:1020 +#: ../../../../inc/config.php:1011 ../../../../inc/config.php:1013 +#: ../../../../inc/config.php:1015 ../../../../inc/config.php:1031 +#: ../../../../inc/config.php:1040 ../../../../inc/config.php:1071 +#: ../../../../inc/config.php:1075 ../../../../inc/config.php:1078 +msgid "You don't have permission to do that." +msgstr "У вас відсутній дозвіл на цю дію." + +#: ../../../../inc/config.php:916 ../../../../inc/config.php:1021 +#: ../../../../inc/config.php:1012 ../../../../inc/config.php:1014 +#: ../../../../inc/config.php:1016 ../../../../inc/config.php:1032 +#: ../../../../inc/config.php:1041 ../../../../inc/config.php:1072 +#: ../../../../inc/config.php:1076 ../../../../inc/config.php:1079 +msgid "That post doesn't exist…" +msgstr "Цей допис не існує…" + +#: ../../../../inc/config.php:917 ../../../../inc/config.php:1022 +#: ../../../../inc/config.php:1013 ../../../../inc/config.php:1015 +#: ../../../../inc/config.php:1017 ../../../../inc/config.php:1033 +#: ../../../../inc/config.php:1042 ../../../../inc/config.php:1073 +#: ../../../../inc/config.php:1077 ../../../../inc/config.php:1080 +msgid "Page not found." +msgstr "Сторінку не знайдено." + +#: ../../../../inc/config.php:918 ../../../../inc/config.php:1023 +#: ../../../../inc/config.php:1014 ../../../../inc/config.php:1016 +#: ../../../../inc/config.php:1018 ../../../../inc/config.php:1034 +#: ../../../../inc/config.php:1043 ../../../../inc/config.php:1074 +#: ../../../../inc/config.php:1078 ../../../../inc/config.php:1081 +#, php-format +msgid "That mod already exists!" +msgstr "Модератор уже існує!" + +#: ../../../../inc/config.php:919 ../../../../inc/config.php:1024 +#: ../../../../inc/config.php:1015 ../../../../inc/config.php:1017 +#: ../../../../inc/config.php:1019 ../../../../inc/config.php:1035 +#: ../../../../inc/config.php:1044 ../../../../inc/config.php:1075 +#: ../../../../inc/config.php:1079 ../../../../inc/config.php:1082 +msgid "That theme doesn't exist!" +msgstr "Цієї теми не існує!" + +# Може, ключ? +#: ../../../../inc/config.php:920 ../../../../inc/config.php:1025 +#: ../../../../inc/config.php:1016 ../../../../inc/config.php:1018 +#: ../../../../inc/config.php:1020 ../../../../inc/config.php:1036 +#: ../../../../inc/config.php:1045 ../../../../inc/config.php:1076 +#: ../../../../inc/config.php:1080 ../../../../inc/config.php:1083 +msgid "Invalid security token! Please go back and try again." +msgstr "Неправильний токен безпеки! Поверніться і спробуйте ще раз." + +#. Default public ban message. In public ban messages, %length% is replaced +#. with "for x days" or +#. "permanently" (with %LENGTH% being the uppercase equivalent). +#. Default public ban message. In public ban messages, %length% is replaced with "for x days" or +#. "permanently" (with %LENGTH% being the uppercase equivalent). +#: ../../../../inc/config.php:1086 ../../../../inc/config.php:1189 +#: ../../../../inc/config.php:1180 ../../../../inc/config.php:1185 +#: ../../../../inc/config.php:1187 ../../../../inc/config.php:1203 +#: ../../../../inc/config.php:1212 ../../../../inc/config.php:1243 +#: ../../../../inc/config.php:1247 ../../../../inc/config.php:1250 +msgid "USER WAS BANNED FOR THIS POST" +msgstr "АВТОРА ЦЬОГО ДОПИСУ ЗАБЛОКОВАНО" + +#: ../../../../inc/mod/pages.php:66 ../../../../inc/mod/pages.php:64 +msgid "Confirm action" +msgstr "Підтвердити дію" + +#: ../../../../inc/mod/pages.php:110 ../../../../inc/mod/pages.php:108 +msgid "Could not find current version! (Check .installed)" +msgstr "Не вдалося знайти поточної версії! (перевірте .installed)" + +#: ../../../../inc/mod/pages.php:162 +msgid "Dashboard" +msgstr "Панель керування" + +#: ../../../../inc/mod/pages.php:267 ../../../../inc/mod/pages.php:265 +msgid "There are no boards to search!" +msgstr "Немає дошок для пошуку!" + +#. $results now contains the search results +#: ../../../../inc/mod/pages.php:335 ../../../../inc/mod/pages.php:334 +msgid "Search results" +msgstr "Результати пошуку" + +#: ../../../../inc/mod/pages.php:436 ../../../../inc/mod/pages.php:438 +msgid "Edit board" +msgstr "Редагувати дошку" + +#: ../../../../inc/mod/pages.php:486 ../../../../inc/mod/pages.php:491 +msgid "Couldn't open board after creation." +msgstr "Не вдалося відкрити дошку після створення." + +#: ../../../../inc/mod/pages.php:506 ../../../../inc/mod/pages.php:511 +msgid "New board" +msgstr "Нова дошка" + +#. line 37 +#: ../../../../inc/mod/pages.php:553 ../../../../inc/mod/pages.php:562 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:121 +msgid "Noticeboard" +msgstr "Дошка оголошень" + +#: ../../../../inc/mod/pages.php:614 ../../../../inc/mod/pages.php:631 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:194 +msgid "News" +msgstr "Новини" + +#: ../../../../inc/mod/pages.php:654 ../../../../inc/mod/pages.php:681 +#: ../../../../inc/mod/pages.php:671 ../../../../inc/mod/pages.php:698 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:300 +#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:75 +msgid "Moderation log" +msgstr "Журнал модерації" + +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#. line 104 +#. line 20 +#. line 18 +#: ../../../../inc/mod/pages.php:838 ../../../../inc/mod/pages.php:852 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:275 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:71 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:64 +#: ../../../../inc/mod/pages.php:849 +msgid "IP" +msgstr "IP" + +#. line 171 +#: ../../../../inc/mod/pages.php:848 ../../../../inc/mod/pages.php:1367 +#: ../../../../inc/mod/pages.php:862 ../../../../inc/mod/pages.php:1432 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:440 +#: ../../../../inc/mod/pages.php:1446 ../../../../inc/mod/pages.php:1447 +#: ../../../../inc/mod/pages.php:859 ../../../../inc/mod/pages.php:1444 +#: ../../../../inc/mod/pages.php:1449 +msgid "New ban" +msgstr "Нове блокування" + +#: ../../../../inc/mod/pages.php:931 ../../../../inc/mod/pages.php:914 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:256 +#: ../../../../inc/mod/pages.php:911 ../../../../inc/mod/pages.php:903 +#: ../../../../templates/themes/public_banlist/theme.php:42 +msgid "Ban list" +msgstr "Список блокувань" + +#. line 38 +#: ../../../../inc/mod/pages.php:1105 ../../../../inc/mod/pages.php:1165 +#: ../../../../inc/mod/pages.php:1172 +#: ../../../../templates/cache/56/25/ac2c51fa6b3e26f9f9ed7dda5224acfbec96881d648c8ded10c5eef2c3e5.php:108 +#: ../../../../inc/mod/pages.php:1173 ../../../../inc/mod/pages.php:1170 +#: ../../../../inc/mod/pages.php:1175 +msgid "Move reply" +msgstr "Перемістити відповідь" + +#: ../../../../inc/mod/pages.php:1131 ../../../../inc/mod/pages.php:1191 +#: ../../../../inc/mod/pages.php:1198 ../../../../inc/mod/pages.php:1199 +#: ../../../../inc/mod/pages.php:1196 ../../../../inc/mod/pages.php:1201 +msgid "Target and source board are the same." +msgstr "Початкова дошка і дошка призначення збігаються." + +#: ../../../../inc/mod/pages.php:1296 ../../../../inc/mod/pages.php:1357 +#: ../../../../inc/mod/pages.php:1371 ../../../../inc/mod/pages.php:1372 +#: ../../../../inc/mod/pages.php:1369 ../../../../inc/mod/pages.php:1374 +msgid "Impossible to move thread; there is only one board." +msgstr "Неможливо перемістити нитку: дошка тільки одна." + +#. line 39 +#: ../../../../inc/mod/pages.php:1300 ../../../../inc/mod/pages.php:1361 +#: ../../../../templates/cache/32/3a/d7e02cef5846ec4f1f423bb0ad2d3c307845d29f70da3f8a90a41f873e7d.php:114 +#: ../../../../inc/mod/pages.php:1375 ../../../../inc/mod/pages.php:1376 +#: ../../../../inc/mod/pages.php:1373 ../../../../inc/mod/pages.php:1378 +msgid "Move thread" +msgstr "Перемістити нитку" + +#: ../../../../inc/mod/pages.php:1698 ../../../../inc/mod/pages.php:1751 +#: ../../../../inc/mod/pages.php:1775 ../../../../inc/mod/pages.php:1791 +#: ../../../../inc/mod/pages.php:1792 ../../../../inc/mod/pages.php:1796 +msgid "Edit user" +msgstr "Редагувати користувача" + +#: ../../../../inc/mod/pages.php:1764 ../../../../inc/mod/pages.php:1855 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:274 +#: ../../../../inc/mod/pages.php:1871 ../../../../inc/mod/pages.php:1872 +#: ../../../../inc/mod/pages.php:1876 +msgid "Manage users" +msgstr "Керування користувачами" + +#. deleted? +#: ../../../../inc/mod/pages.php:1826 ../../../../inc/mod/pages.php:1899 +#: ../../../../inc/mod/pages.php:1945 ../../../../inc/mod/pages.php:2021 +#: ../../../../inc/mod/pages.php:1961 ../../../../inc/mod/pages.php:2037 +#: ../../../../inc/mod/pages.php:1962 ../../../../inc/mod/pages.php:2038 +#: ../../../../inc/mod/pages.php:1966 ../../../../inc/mod/pages.php:2042 +msgid "New PM for" +msgstr "Нове приватне повідомлення до" + +#: ../../../../inc/mod/pages.php:1830 ../../../../inc/mod/pages.php:1952 +#: ../../../../inc/mod/pages.php:1968 ../../../../inc/mod/pages.php:1969 +#: ../../../../inc/mod/pages.php:1973 +msgid "Private message" +msgstr "Приватне повідомлення" + +#. line 68 +#: ../../../../inc/mod/pages.php:1851 ../../../../inc/mod/pages.php:1973 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:200 +#: ../../../../inc/mod/pages.php:1989 ../../../../inc/mod/pages.php:1990 +#: ../../../../inc/mod/pages.php:1994 +msgid "PM inbox" +msgstr "Скринька вхідних приватних повідомлень" + +#: ../../../../inc/mod/pages.php:1963 ../../../../inc/mod/pages.php:1967 +#: ../../../../inc/mod/pages.php:2090 ../../../../inc/mod/pages.php:2094 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:309 +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:115 +#: ../../../../inc/mod/pages.php:2106 ../../../../inc/mod/pages.php:2110 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:318 +#: ../../../../inc/mod/pages.php:2107 ../../../../inc/mod/pages.php:2111 +#: ../../../../inc/mod/pages.php:2115 +msgid "Rebuild" +msgstr "Перебудувати" + +#: ../../../../inc/mod/pages.php:2043 ../../../../inc/mod/pages.php:2179 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:238 +#: ../../../../inc/mod/pages.php:2195 ../../../../inc/mod/pages.php:2196 +#: ../../../../inc/mod/pages.php:2200 +msgid "Report queue" +msgstr "Черга скарг" + +#: ../../../../inc/mod/pages.php:2111 ../../../../inc/mod/pages.php:2210 +#: ../../../../inc/mod/pages.php:2256 ../../../../inc/mod/pages.php:2350 +#: ../../../../inc/mod/pages.php:2334 ../../../../inc/mod/pages.php:2428 +#: ../../../../inc/mod/pages.php:2328 ../../../../inc/mod/pages.php:2422 +#: ../../../../inc/mod/pages.php:2327 ../../../../inc/mod/pages.php:2421 +#: ../../../../inc/mod/pages.php:2332 ../../../../inc/mod/pages.php:2426 +msgid "Config editor" +msgstr "Редактор конфігурації" + +#: ../../../../inc/mod/pages.php:2226 ../../../../inc/mod/pages.php:2367 +#: ../../../../inc/mod/pages.php:2445 ../../../../inc/mod/pages.php:2439 +#: ../../../../inc/mod/pages.php:2438 ../../../../inc/mod/pages.php:2443 +msgid "Themes directory doesn't exist!" +msgstr "Каталог з темами не існує!" + +#: ../../../../inc/mod/pages.php:2228 ../../../../inc/mod/pages.php:2369 +#: ../../../../inc/mod/pages.php:2447 ../../../../inc/mod/pages.php:2441 +#: ../../../../inc/mod/pages.php:2440 ../../../../inc/mod/pages.php:2445 +msgid "Cannot open themes directory; check permissions." +msgstr "Не вдалося відкрити каталог з темами, перевірте дозволи." + +#: ../../../../inc/mod/pages.php:2242 ../../../../inc/mod/pages.php:2388 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:291 +#: ../../../../inc/mod/pages.php:2466 ../../../../inc/mod/pages.php:2460 +#: ../../../../inc/mod/pages.php:2459 ../../../../inc/mod/pages.php:2464 +msgid "Manage themes" +msgstr "Керування темами" + +#: ../../../../inc/mod/pages.php:2307 ../../../../inc/mod/pages.php:2453 +#: ../../../../inc/mod/pages.php:2531 ../../../../inc/mod/pages.php:2525 +#: ../../../../inc/mod/pages.php:2524 ../../../../inc/mod/pages.php:2529 +#, php-format +msgid "Installed theme: %s" +msgstr "Встановлена теми: %s" + +#: ../../../../inc/mod/pages.php:2318 ../../../../inc/mod/pages.php:2464 +#: ../../../../inc/mod/pages.php:2542 ../../../../inc/mod/pages.php:2536 +#: ../../../../inc/mod/pages.php:2535 ../../../../inc/mod/pages.php:2540 +#, php-format +msgid "Configuring theme: %s" +msgstr "Конфігурується тема: %s" + +#: ../../../../inc/mod/pages.php:2346 ../../../../inc/mod/pages.php:2493 +#: ../../../../inc/mod/pages.php:2571 ../../../../inc/mod/pages.php:2565 +#: ../../../../inc/mod/pages.php:2564 ../../../../inc/mod/pages.php:2569 +#, php-format +msgid "Rebuilt theme: %s" +msgstr "Перебудовано тему: %s" + +#: ../../../../inc/mod/pages.php:2385 ../../../../inc/mod/pages.php:2532 +#: ../../../../inc/mod/pages.php:2610 ../../../../inc/mod/pages.php:2604 +#: ../../../../inc/mod/pages.php:2603 ../../../../inc/mod/pages.php:2608 +msgid "Debug: Anti-spam" +msgstr "Зневадження: Антиспам" + +#: ../../../../inc/mod/pages.php:2409 ../../../../inc/mod/pages.php:2566 +#: ../../../../inc/mod/pages.php:2644 ../../../../inc/mod/pages.php:2638 +#: ../../../../inc/mod/pages.php:2637 ../../../../inc/mod/pages.php:2642 +msgid "Debug: Recent posts" +msgstr "Зневадження: Останні пости" + +#: ../../../../inc/mod/pages.php:2433 ../../../../inc/mod/pages.php:2590 +#: ../../../../inc/mod/pages.php:2668 ../../../../inc/mod/pages.php:2662 +#: ../../../../inc/mod/pages.php:2661 ../../../../inc/mod/pages.php:2666 +msgid "Debug: SQL" +msgstr "Зневадження: SQL" + +#. Print error +#: ../../../../inc/database.php:72 ../../../../inc/database.php:94 +msgid "Database error: " +msgstr "Помилка бази даних:" + +#: ../../../../banned.php:4 +msgid "Banned?" +msgstr "Заблоковано?" + +#: ../../../../banned.php:5 +msgid "You are not banned." +msgstr "Вас не заблоковано." + +#. line 6 +#: ../../../../templates/cache/3c/80/0ebbee302f4fad8d0d7f13e62db5.php:41 +#: ../../../../templates/cache/e1/4c/f58701138b0d44bc13ada3e46deec60da83d42ff4f39720ccd6955b641f7.php:44 +msgid "Go back" +msgstr "Повернутися" + +#. line 13 +#: ../../../../templates/cache/3c/80/0ebbee302f4fad8d0d7f13e62db5.php:56 +#: ../../../../templates/cache/e1/4c/f58701138b0d44bc13ada3e46deec60da83d42ff4f39720ccd6955b641f7.php:59 +msgid "Error information" +msgstr "Інформація про помилку" + +#. line 2 +#. line 3 +#: ../../../../templates/cache/82/40/4c4a4b82f787181e6500ce83494d.php:22 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:25 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:27 +msgid "Delete Post" +msgstr "Видалити допис" + +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 84 +#. line 3 +#. line 97 +#. line 3 +#. line 97 +#. line 3 +#. line 97 +#. line 4 +#. line 97 +#. line 4 +#. line 97 +#. line 4 +#. line 97 +#. line 4 +#. line 97 +#. line 4 +#: ../../../../templates/cache/82/40/4c4a4b82f787181e6500ce83494d.php:26 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:250 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:253 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:29 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:290 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:31 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:301 +msgid "File" +msgstr "Файл" + +#. line 132 +#. line 14 +#. line 132 +#. line 14 +#. line 131 +#. line 14 +#. line 131 +#. line 14 +#. line 131 +#. line 14 +#. line 131 +#. line 21 +#. line 14 +#. line 131 +#. line 14 +#. line 144 +#. line 14 +#. line 144 +#. line 21 +#. line 14 +#. line 144 +#. line 21 +#. line 14 +#. line 144 +#. line 21 +#. line 14 +#: ../../../../templates/cache/82/40/4c4a4b82f787181e6500ce83494d.php:28 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:364 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:367 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:31 +#: ../../../../templates/cache/00/31/a027d7b6d57819b6e43e58620f3f4c76194dd75db65fc888a5053ce62803.php:48 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:363 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:69 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:400 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:33 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:411 +msgid "Password" +msgstr "Пароль" + +#. line 8 +#. line 108 +#. line 32 +#. line 9 +#. line 23 +#. line 8 +#. line 108 +#. line 32 +#. line 8 +#. line 32 +#. line 23 +#. line 8 +#. line 108 +#. line 32 +#. line 9 +#. line 23 +#. line 8 +#. line 5 +#. line 8 +#. line 108 +#. line 32 +#. line 9 +#. line 23 +#. line 5 +#. line 8 +#. line 108 +#. line 32 +#. line 9 +#. line 23 +#. line 48 +#. line 5 +#. line 8 +#. line 108 +#. line 32 +#. line 9 +#. line 23 +#. line 48 +#. line 5 +#. line 8 +#. line 108 +#. line 32 +#. line 5 +#. line 8 +#. line 108 +#. line 32 +#. line 23 +#. line 5 +#. line 8 +#. line 108 +#. line 32 +#. line 23 +#. line 5 +#. line 10 +#. line 108 +#. line 32 +#. line 9 +#. line 23 +#. line 48 +#. line 5 +#. line 10 +#. line 108 +#. line 32 +#. line 9 +#. line 23 +#. line 48 +#. line 10 +#. line 5 +#. line 10 +#. line 108 +#. line 32 +#. line 23 +#. line 48 +#: ../../../../templates/cache/82/40/4c4a4b82f787181e6500ce83494d.php:39 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:42 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:285 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:99 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:43 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:77 +#: ../../../../templates/cache/3a/62/f804928dbcf285e3d5d8d7ae31b1e3a7c78264f270efa9650d31f69c7897.php:37 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:141 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:47 +msgid "Reason" +msgstr "Причина" + +#. line 10 +#. line 12 +#: ../../../../templates/cache/82/40/4c4a4b82f787181e6500ce83494d.php:44 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:47 +#: ../../../../templates/cache/17/2f/ea79f6d94768f645ed33b3f5c1a54caee235af04d24b88e34cc8c2d48583.php:52 +msgid "Report" +msgstr "Поскаржитися" + +#: ../../../../templates/cache/f5/e3/343716327c6183713f70a3fb57f1.php:149 +#: ../../../../templates/cache/aa/f6/f10fd83961bcd8c947af6ddf919d.php:134 +#: ../../../../templates/cache/62/8c/21348d46377c3e1b3f8c476ba376.php:65 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:137 +#: ../../../../templates/cache/26/b3/eb11457d26f281883c21fad69f55f2c85f7cde1e4986db8692a12aaf72a5.php:153 +#: ../../../../templates/cache/e5/67/00152f100a684a6ff580e1afded8e907bdea04b4818d725ebfeb103d70d9.php:68 +#: ../../../../templates/cache/e5/67/00152f100a684a6ff580e1afded8e907bdea04b4818d725ebfeb103d70d9.php:71 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:120 +#: ../../../../templates/cache/26/b3/eb11457d26f281883c21fad69f55f2c85f7cde1e4986db8692a12aaf72a5.php:138 +#: ../../../../templates/cache/26/b3/eb11457d26f281883c21fad69f55f2c85f7cde1e4986db8692a12aaf72a5.php:129 +msgid "Return to dashboard" +msgstr "Повернутися до панелі керування" + +#. line 39 +#. line 33 +#: ../../../../templates/cache/aa/f6/f10fd83961bcd8c947af6ddf919d.php:143 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:146 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:134 +msgid "Posting mode: Reply" +msgstr "Режим дописування: відповідь" + +#: ../../../../templates/cache/aa/f6/f10fd83961bcd8c947af6ddf919d.php:147 +#: ../../../../templates/cache/aa/f6/f10fd83961bcd8c947af6ddf919d.php:200 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:150 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:203 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:138 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:186 +msgid "Return" +msgstr "Повернутися" + +#: ../../../../templates/cache/f3/ad/68dee281a64ebad9a5c774b53279.php:61 +#: ../../../../templates/cache/d2/14/70c07e4c5f648cfa0d0663a1f18973ff6f6946363b45332b2627a0fcf273.php:64 +msgid "(No news to show.)" +msgstr "(немає новин)" + +#: ../../../../templates/cache/f3/ad/68dee281a64ebad9a5c774b53279.php:85 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:146 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:116 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:144 +#: ../../../../templates/cache/d2/14/70c07e4c5f648cfa0d0663a1f18973ff6f6946363b45332b2627a0fcf273.php:88 +msgid "no subject" +msgstr "без заголовка" + +#. line 44 +#. line 56 +#. line 44 +#. line 56 +#. line 44 +#: ../../../../templates/cache/f3/ad/68dee281a64ebad9a5c774b53279.php:91 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:125 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:153 +#: ../../../../templates/cache/d2/14/70c07e4c5f648cfa0d0663a1f18973ff6f6946363b45332b2627a0fcf273.php:94 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:124 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:132 +msgid "by" +msgstr "модератором/адміністратором" + +#. line 50 +#: ../../../../templates/cache/f3/ad/68dee281a64ebad9a5c774b53279.php:95 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:146 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:157 +#: ../../../../templates/cache/d2/14/70c07e4c5f648cfa0d0663a1f18973ff6f6946363b45332b2627a0fcf273.php:98 +msgid "at" +msgstr "о" + +#. line 28 +#. line 26 +#: ../../../../templates/cache/4b/3e/915cc5ac5fe144c331207c656528.php:99 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:102 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:88 +msgid "1 reply" +msgid_plural "%count% replies" +msgstr[0] "%count% відповідь" +msgstr[1] "%count% відповіді" +msgstr[2] "%count% відповідей" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:102 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:105 +msgid "File:" +msgstr "Файл:" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:115 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:127 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:165 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:206 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:118 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:130 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:168 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:209 +#: ../../../../templates/cache/9b/6a/0d0c5add399e8dfbd5c8c097ea68815306312248498dae4682282190e561.php:128 +#: ../../../../templates/cache/f4/a7/ad2833eb0c0460ae8ae508f0d0846fd7a06aafcf8ef126ae76721e92f42a.php:82 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:141 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:179 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:220 +msgid "Spoiler Image" +msgstr "Спойлерне зображення" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:530 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:495 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:506 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:532 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:591 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:304 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:216 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:227 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:229 +msgid "Reply" +msgstr "Відповісти" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:544 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:509 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:520 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:546 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:605 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:318 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:230 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:241 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:243 +msgid "View All" +msgstr "Показати все" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:561 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:526 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:537 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:563 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:622 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:335 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:247 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:258 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:260 +msgid "Last 1 Post" +msgid_plural "Last %count% Posts" +msgstr[0] "Останній %count% допис" +msgstr[1] "Останні %count% дописи" +msgstr[2] "Останні %count% дописів" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:598 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:563 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:574 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:600 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:659 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:372 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:284 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:295 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:297 +msgid "1 post" +msgid_plural "%count% posts" +msgstr[0] "%count% допис" +msgstr[1] "%count% дописи" +msgstr[2] "%count% дописів" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:604 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:569 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:580 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:116 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:606 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:665 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:378 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:290 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:301 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:303 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:141 +msgid "and" +msgstr "і" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:616 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:581 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:592 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:618 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:677 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:390 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:302 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:313 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:315 +msgid "1 image reply" +msgid_plural "%count% image replies" +msgstr[0] "%count% допис із зображеннями" +msgstr[1] "%count% дописи із зображеннями" +msgstr[2] "%count% дописів із зображеннями" + +#: ../../../../templates/cache/d8/f2/7780eb1adcdbda7e332659e3fb4f.php:621 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:586 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:597 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:623 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:682 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:395 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:307 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:318 +#: ../../../../templates/cache/b5/eb/fd7d06d38210e123d492fb7f2a1891578af746ef421003f1b55da157122f.php:320 +msgid "omitted. Click reply to view." +msgstr "пропущено. Натисніть «Відповісти», щоб побачити." + +#. line 7 +#. line 14 +#. line 7 +#. line 14 +#. line 8 +#. line 14 +#. line 7 +#. line 8 +#. line 14 +#. line 8 +#. line 14 +#. line 8 +#. line 14 +#. line 7 +#. line 8 +#. line 7 +#. line 14 +#. line 8 +#. line 14 +#. line 7 +#. line 8 +#. line 14 +#. line 7 +#. line 8 +#. line 14 +#. line 7 +#. line 8 +#. line 14 +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:30 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:66 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:38 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:42 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:48 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:69 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:33 +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:40 +msgid "Name" +msgstr "Імʼя" + +#. line 15 +#. line 24 +#. line 15 +#. line 24 +#. line 15 +#. line 24 +#. line 15 +#. line 24 +#. line 15 +#. line 24 +#. line 15 +#. line 24 +#. line 15 +#. line 24 +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:44 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:88 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:91 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:47 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:97 +msgid "Email" +msgstr "Ел. пошта" + +#. line 23 +#. line 46 +#. line 23 +#. line 46 +#. line 12 +#. line 24 +#. line 46 +#. line 23 +#. line 12 +#. line 46 +#. line 12 +#. line 46 +#. line 12 +#. line 46 +#. line 23 +#. line 12 +#. line 24 +#. line 46 +#. line 12 +#. line 46 +#. line 23 +#. line 12 +#. line 46 +#. line 23 +#. line 12 +#. line 46 +#. line 23 +#. line 12 +#. line 46 +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:58 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:147 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:48 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:76 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:150 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:61 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:161 +msgid "Subject" +msgstr "Тема" + +#. line 27 +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:68 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:71 +msgid "Update" +msgstr "Оновлення" + +#. line 32 +#. line 57 +#. line 32 +#. line 57 +#. line 32 +#. line 57 +#. line 32 +#. line 57 +#. line 32 +#. line 57 +#. line 32 +#. line 57 +#. line 32 +#. line 57 +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:76 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:178 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:181 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:79 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:192 +msgid "Comment" +msgstr "Допис" + +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:97 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:100 +msgid "Currently editing raw HTML." +msgstr "Зараз редагується сирий HTML" + +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:105 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:108 +msgid "Edit markup instead?" +msgstr "Редагувати натомість розмітку?" + +#: ../../../../templates/cache/39/42/cbc36382096edfa72a8bc26e4514.php:115 +#: ../../../../templates/cache/d1/2d/e4ea563232b42da227befa9cf03fef935e472b1268221e2399d8d6af1cd5.php:118 +msgid "Edit raw HTML instead?" +msgstr "Редагувати натомість сирий HTML?" + +#. line 73 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:226 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:229 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:240 +msgid "Verification" +msgstr "Підтвердження" + +#. line 90 +#. line 103 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:262 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:265 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:302 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:313 +msgid "Or URL" +msgstr "Або URL" + +#. line 100 +#. line 113 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:282 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:285 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:322 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:333 +msgid "Embed" +msgstr "Вбудувати" + +#. line 112 +#. line 111 +#. line 124 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:306 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:309 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:305 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:342 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:353 +msgid "Flags" +msgstr "Позначки" + +#. line 116 +#. line 117 +#. line 116 +#. line 117 +#. line 116 +#. line 117 +#. line 116 +#. line 117 +#. line 115 +#. line 116 +#. line 115 +#. line 116 +#. line 115 +#. line 116 +#. line 115 +#. line 116 +#. line 115 +#. line 116 +#. line 115 +#. line 116 +#. line 115 +#. line 116 +#. line 128 +#. line 129 +#. line 128 +#. line 129 +#. line 128 +#. line 129 +#. line 128 +#. line 129 +#. line 128 +#. line 129 +#. line 128 +#. line 129 +#. line 128 +#. line 129 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:316 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:320 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:319 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:323 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:315 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:352 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:356 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:363 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:367 +msgid "Sticky" +msgstr "Прикріплений" + +#. line 120 +#. line 121 +#. line 120 +#. line 121 +#. line 120 +#. line 121 +#. line 120 +#. line 121 +#. line 119 +#. line 120 +#. line 119 +#. line 120 +#. line 119 +#. line 120 +#. line 119 +#. line 120 +#. line 119 +#. line 120 +#. line 119 +#. line 120 +#. line 119 +#. line 120 +#. line 132 +#. line 133 +#. line 132 +#. line 133 +#. line 132 +#. line 133 +#. line 132 +#. line 133 +#. line 132 +#. line 133 +#. line 132 +#. line 133 +#. line 132 +#. line 133 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:330 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:334 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:333 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:337 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:329 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:366 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:370 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:377 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:381 +msgid "Lock" +msgstr "Закритий" + +#. line 124 +#. line 125 +#. line 124 +#. line 125 +#. line 124 +#. line 125 +#. line 124 +#. line 125 +#. line 123 +#. line 124 +#. line 123 +#. line 124 +#. line 123 +#. line 124 +#. line 123 +#. line 124 +#. line 123 +#. line 124 +#. line 123 +#. line 124 +#. line 123 +#. line 124 +#. line 136 +#. line 137 +#. line 136 +#. line 137 +#. line 136 +#. line 137 +#. line 136 +#. line 137 +#. line 136 +#. line 137 +#. line 136 +#. line 137 +#. line 136 +#. line 137 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:344 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:348 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:347 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:351 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:343 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:380 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:384 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:391 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:395 +msgid "Raw HTML" +msgstr "Сирий HTML" + +#. line 137 +#. line 136 +#. line 141 +#. line 154 +#: ../../../../templates/cache/0c/37/9331df01df7c2986d77a02d3beb0.php:374 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:377 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:373 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:378 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:415 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:426 +msgid "(For file deletion.)" +msgstr "(для видалення файла)" + +#: ../../../../search.php:5 +msgid "Post search is disabled" +msgstr "Пошук дописів вимкнено" + +#: ../../../../search.php:25 ../../../../search.php:31 +#: ../../../../search.php:29 ../../../../search.php:35 +msgid "Wait a while before searching again, please." +msgstr "Будь ласка, зачекайте трохи, перш ніж знову шукати." + +#: ../../../../search.php:131 ../../../../search.php:135 +msgid "Query too broad." +msgstr "Запит надто загальний." + +#: ../../../../search.php:152 ../../../../search.php:156 +#, php-format +msgid "%d result in" +msgid_plural "%d results in" +msgstr[0] "%d результат у" +msgstr[1] "%d результати у" +msgstr[2] "%d результатів у" + +#: ../../../../search.php:163 ../../../../search.php:167 +msgid "No results." +msgstr "Нема результатів." + +#. line 115 +#. line 16 +#. line 115 +#. line 16 +#. line 115 +#. line 16 +#. line 115 +#. line 16 +#. line 2 +#. line 13 +#. line 115 +#. line 16 +#. line 2 +#. line 13 +#. line 115 +#. line 16 +#. line 2 +#. line 13 +#. line 115 +#. line 16 +#. line 2 +#. line 13 +#. line 2 +#. line 115 +#. line 16 +#. line 2 +#. line 13 +#. line 2 +#. line 115 +#. line 16 +#. line 2 +#. line 13 +#. line 115 +#. line 16 +#. line 115 +#. line 16 +#. line 2 +#. line 13 +#. line 118 +#. line 16 +#. line 2 +#. line 13 +#. line 2 +#. line 118 +#. line 16 +#. line 2 +#. line 13 +#. line 2 +#. line 13 +#. line 118 +#. line 16 +#. line 22 +#. line 2 +#. line 13 +#. line 2 +#: ../../../../search.php:168 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:334 +#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:83 +#: ../../../../templates/cache/cb/8b/63013711213735996df92becb7bd43d753c51314cfe5433c562706333eb0.php:25 +#: ../../../../templates/cache/cb/8b/63013711213735996df92becb7bd43d753c51314cfe5433c562706333eb0.php:66 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:25 +#: ../../../../search.php:172 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:343 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:61 +msgid "Search" +msgstr "Пошук" + +#: ../../../../inc/mod/pages.php:939 ../../../../inc/mod/pages.php:936 +#: ../../../../inc/mod/pages.php:941 +msgid "Ban appeal not found!" +msgstr "Оскарження блокування не знайдено!" + +#: ../../../../inc/mod/pages.php:989 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:265 +#: ../../../../inc/mod/pages.php:994 ../../../../inc/mod/pages.php:995 +#: ../../../../inc/mod/pages.php:992 ../../../../inc/mod/pages.php:997 +msgid "Ban appeals" +msgstr "Оскарження блокувань" + +#: ../../../../inc/mod/pages.php:1833 ../../../../inc/mod/pages.php:1849 +#: ../../../../inc/mod/pages.php:1850 ../../../../inc/mod/pages.php:1854 +msgid "New user" +msgstr "Новий користувач" + +#: ../../../../inc/mod/pages.php:1888 ../../../../inc/mod/pages.php:1904 +#: ../../../../inc/mod/pages.php:1905 ../../../../inc/mod/pages.php:1909 +msgid "Impossible to promote/demote user." +msgstr "Неможливо підвищити/понизити користувача." + +#: ../../../../inc/mod/pages.php:2612 ../../../../inc/mod/pages.php:2690 +#: ../../../../inc/mod/pages.php:2684 ../../../../inc/mod/pages.php:2683 +#: ../../../../inc/mod/pages.php:2688 +msgid "Debug: APC" +msgstr "Зневадження: APC" + +#: ../../../../inc/config.php:1026 ../../../../inc/config.php:1017 +#: ../../../../inc/config.php:1019 ../../../../inc/config.php:1021 +#: ../../../../inc/config.php:1037 ../../../../inc/config.php:1046 +#: ../../../../inc/config.php:1077 ../../../../inc/config.php:1081 +#: ../../../../inc/config.php:1084 +msgid "" +"Your code contained PHP syntax errors. Please go back and correct them. PHP " +"says: " +msgstr "" +"Ваш код містить синтаксичні помилки PHP. Будь ласка, поверніться і виправте " +"їх. Повідомлення PHP:" + +#. line 2 +#. line 6 +#. line 2 +#. line 6 +#. line 2 +#. line 46 +#. line 6 +#. line 2 +#. line 46 +#. line 6 +#. line 2 +#. line 46 +#. line 6 +#. line 2 +#. line 46 +#. line 6 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:25 +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:38 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:139 +msgid "Boards" +msgstr "Дошки" + +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:79 +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:215 +msgid "edit" +msgstr "редагування" + +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:97 +msgid "Create new board" +msgstr "Створити нову дошку" + +#. line 32 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:108 +msgid "Messages" +msgstr "Повідомлення" + +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:188 +msgid "View all noticeboard entries" +msgstr "Переглянути всі записи дошки оголошень" + +#. line 76 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:222 +msgid "Administration" +msgstr "Адміністрування" + +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:282 +msgid "Change password" +msgstr "Змінити пароль" + +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:318 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:327 +msgid "Configuration" +msgstr "Конфігурування" + +#. line 127 +#. line 130 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:357 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:366 +msgid "Other" +msgstr "Інше" + +#. line 139 +#. line 142 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:391 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:400 +msgid "Debug" +msgstr "Зневадження" + +#. line 141 +#. line 144 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:396 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:405 +msgid "Anti-spam" +msgstr "Антиспам" + +#. line 142 +#. line 145 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:400 +#: ../../../../inc/mod/pages.php:2288 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:309 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:409 +#: ../../../../inc/mod/pages.php:2283 ../../../../inc/mod/pages.php:2282 +#: ../../../../inc/mod/pages.php:2287 +msgid "Recent posts" +msgstr "Останні дописи" + +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:407 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:416 +msgid "SQL" +msgstr "SQL" + +#. line 164 +#. line 167 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:446 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:455 +msgid "User account" +msgstr "Обліковий запис користувача" + +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:454 +#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:463 +msgid "Logout" +msgstr "Вийти" + +#. line 3 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:27 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:27 +msgid "New post" +msgstr "Новий допис" + +#. line 16 +#. line 28 +#. line 16 +#. line 28 +#. line 16 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:55 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:83 +msgid "Body" +msgstr "Вміст" + +#. line 21 +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:63 +msgid "Post to noticeboard" +msgstr "Дописати на дошці оголошень" + +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:90 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:118 +msgid "delete" +msgstr "видалити" + +#: ../../../../templates/cache/39/f9/8228f77b382baf1d61c1215dc0f0236e4cdf8cc5e938259785cf3e67d1ca.php:138 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:123 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:405 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:504 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:251 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:197 +#: ../../../../templates/cache/f8/05/d647b76d6ba28842b313895b0d435295026f1912dc49639bd64e3b42eba3.php:70 +#: ../../../../templates/cache/f9/e9/d592e3c89e2f76520cf989aa8359d3d143d8fa4996ff1d97b3be51f87a05.php:42 +#: ../../../../templates/cache/f9/e9/d592e3c89e2f76520cf989aa8359d3d143d8fa4996ff1d97b3be51f87a05.php:67 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:99 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:345 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:415 +msgid "deleted?" +msgstr "видалено?" + +#. line 33 +#: ../../../../templates/cache/1a/7f/6eb467b2d978da59cfea2fe64f8898a5fb769be35fbb2ec50691da9a3d52.php:91 +msgid "Post news entry" +msgstr "Розмістити новину" + +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 15 +#. line 67 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 67 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 15 +#. line 3 +#. line 67 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 15 +#. line 67 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 15 +#. line 3 +#. line 67 +#. line 17 +#. line 54 +#. line 133 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 15 +#. line 3 +#. line 67 +#. line 17 +#. line 54 +#. line 133 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 67 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 67 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 15 +#. line 3 +#. line 67 +#. line 17 +#. line 54 +#. line 133 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 15 +#. line 3 +#. line 67 +#. line 17 +#. line 54 +#. line 133 +#. line 24 +#. line 63 +#. line 152 +#. line 182 +#. line 3 +#. line 67 +#. line 17 +#. line 54 +#. line 133 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:81 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:186 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:391 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:464 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:67 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:183 +#: ../../../../templates/cache/f8/05/d647b76d6ba28842b313895b0d435295026f1912dc49639bd64e3b42eba3.php:26 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:59 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:165 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:371 +msgid "Staff" +msgstr "Модератор" + +#. line 25 +#. line 68 +#. line 25 +#. line 68 +#. line 25 +#. line 68 +#. line 25 +#. line 68 +#. line 25 +#. line 68 +#. line 18 +#. line 25 +#. line 68 +#. line 18 +#. line 25 +#. line 68 +#. line 25 +#. line 68 +#. line 25 +#. line 68 +#. line 25 +#. line 68 +#. line 18 +#. line 25 +#. line 68 +#. line 18 +#. line 25 +#. line 68 +#. line 18 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:85 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:197 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:63 +msgid "Note" +msgstr "Примітка" + +#. line 26 +#. line 22 +#. line 26 +#. line 19 +#. line 26 +#. line 19 +#. line 26 +#. line 22 +#. line 26 +#. line 19 +#. line 22 +#. line 26 +#. line 19 +#. line 26 +#. line 19 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:89 +#: ../../../../templates/cache/f9/e9/d592e3c89e2f76520cf989aa8359d3d143d8fa4996ff1d97b3be51f87a05.php:79 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:67 +msgid "Date" +msgstr "Дата" + +#. line 25 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:96 +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:88 +msgid "Actions" +msgstr "Дії" + +#. line 49 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:154 +msgid "remove" +msgstr "прибрати" + +#. line 76 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:208 +msgid "New note" +msgstr "Нова примітка" + +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#. line 94 +#. line 7 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:251 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:36 +msgid "Status" +msgstr "Статус" + +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:259 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:44 +msgid "Expired" +msgstr "Спливає" + +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:265 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:50 +msgid "Active" +msgstr "Активний" + +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:299 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:91 +msgid "no reason" +msgstr "без причини" + +#. line 118 +#. line 184 +#. line 65 +#. line 10 +#. line 33 +#. line 118 +#. line 184 +#. line 65 +#. line 33 +#. line 118 +#. line 184 +#. line 65 +#. line 10 +#. line 6 +#. line 33 +#. line 3 +#. line 118 +#. line 184 +#. line 65 +#. line 10 +#. line 33 +#. line 3 +#. line 118 +#. line 184 +#. line 65 +#. line 10 +#. line 6 +#. line 33 +#. line 49 +#. line 136 +#. line 3 +#. line 95 +#. line 118 +#. line 184 +#. line 65 +#. line 10 +#. line 6 +#. line 33 +#. line 49 +#. line 136 +#. line 3 +#. line 118 +#. line 184 +#. line 65 +#. line 3 +#. line 118 +#. line 184 +#. line 65 +#. line 33 +#. line 3 +#. line 118 +#. line 184 +#. line 65 +#. line 33 +#. line 3 +#. line 95 +#. line 118 +#. line 184 +#. line 65 +#. line 10 +#. line 6 +#. line 33 +#. line 49 +#. line 136 +#. line 3 +#. line 95 +#. line 118 +#. line 184 +#. line 65 +#. line 10 +#. line 6 +#. line 33 +#. line 49 +#. line 136 +#. line 3 +#. line 95 +#. line 118 +#. line 184 +#. line 65 +#. line 6 +#. line 33 +#. line 49 +#. line 136 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:309 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:472 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:160 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:47 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:101 +#: ../../../../templates/cache/f8/05/d647b76d6ba28842b313895b0d435295026f1912dc49639bd64e3b42eba3.php:38 +#: ../../../../templates/cache/3a/62/f804928dbcf285e3d5d8d7ae31b1e3a7c78264f270efa9650d31f69c7897.php:26 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:145 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:383 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:263 +msgid "Board" +msgstr "Дошка" + +#. line 71 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:323 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:169 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:133 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:115 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:73 +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:100 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:227 +msgid "all boards" +msgstr "всі дошки" + +#. line 128 +#. line 11 +#. line 43 +#. line 128 +#. line 43 +#. line 128 +#. line 11 +#. line 43 +#. line 128 +#. line 11 +#. line 43 +#. line 128 +#. line 11 +#. line 43 +#. line 50 +#. line 128 +#. line 11 +#. line 43 +#. line 50 +#. line 128 +#. line 43 +#. line 128 +#. line 43 +#. line 128 +#. line 11 +#. line 43 +#. line 50 +#. line 128 +#. line 11 +#. line 43 +#. line 50 +#. line 128 +#. line 43 +#. line 50 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:333 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:51 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:125 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:149 +msgid "Set" +msgstr "Встановити" + +#. line 132 +#. line 13 +#. line 47 +#. line 132 +#. line 47 +#. line 132 +#. line 13 +#. line 47 +#. line 132 +#. line 13 +#. line 47 +#. line 132 +#. line 13 +#. line 47 +#. line 52 +#. line 132 +#. line 13 +#. line 47 +#. line 52 +#. line 132 +#. line 47 +#. line 132 +#. line 47 +#. line 132 +#. line 13 +#. line 47 +#. line 52 +#. line 132 +#. line 13 +#. line 47 +#. line 52 +#. line 132 +#. line 47 +#. line 52 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:343 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:59 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:135 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:157 +msgid "Expires" +msgstr "Спливає" + +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:357 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:173 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:149 +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:155 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:267 +msgid "never" +msgstr "ніколи" + +#. line 142 +#. line 14 +#. line 57 +#. line 142 +#. line 57 +#. line 142 +#. line 14 +#. line 57 +#. line 142 +#. line 14 +#. line 57 +#. line 142 +#. line 14 +#. line 57 +#. line 53 +#. line 142 +#. line 14 +#. line 57 +#. line 53 +#. line 142 +#. line 57 +#. line 142 +#. line 57 +#. line 142 +#. line 14 +#. line 57 +#. line 53 +#. line 142 +#. line 14 +#. line 57 +#. line 53 +#. line 142 +#. line 57 +#. line 53 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:367 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:63 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:159 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:161 +msgid "Seen" +msgstr "Побачено" + +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:375 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:201 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:167 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:295 +msgid "Yes" +msgstr "Так" + +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:381 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:207 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:173 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:301 +msgid "No" +msgstr "Ні" + +#. line 163 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:419 +msgid "Remove ban" +msgstr "Прибрати блокування" + +#. line 183 +#. line 5 +#. line 183 +#. line 5 +#. line 135 +#. line 94 +#. line 183 +#. line 5 +#. line 135 +#. line 183 +#. line 94 +#. line 183 +#. line 5 +#. line 135 +#. line 94 +#. line 183 +#. line 5 +#. line 135 +#. line 94 +#. line 183 +#. line 5 +#. line 135 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:468 +#: ../../../../templates/cache/f8/05/d647b76d6ba28842b313895b0d435295026f1912dc49639bd64e3b42eba3.php:34 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:379 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:259 +msgid "Time" +msgstr "Час" + +#. line 185 +#. line 89 +#. line 185 +#. line 89 +#. line 185 +#. line 7 +#. line 89 +#. line 185 +#. line 89 +#. line 185 +#. line 7 +#. line 89 +#. line 137 +#. line 96 +#. line 185 +#. line 7 +#. line 89 +#. line 137 +#. line 185 +#. line 89 +#. line 185 +#. line 89 +#. line 96 +#. line 185 +#. line 7 +#. line 89 +#. line 137 +#. line 96 +#. line 185 +#. line 7 +#. line 89 +#. line 137 +#. line 96 +#. line 185 +#. line 7 +#. line 89 +#. line 137 +#: ../../../../templates/cache/b1/4c/16a427b0d49ecf353c259d9fb606841783484eca9d790e766fdf0e3e9754.php:476 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:234 +#: ../../../../templates/cache/f8/05/d647b76d6ba28842b313895b0d435295026f1912dc49639bd64e3b42eba3.php:42 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:387 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:267 +msgid "Action" +msgstr "Дія" + +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:73 +msgid "(or subnet)" +msgstr "(або підмережа)" + +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:88 +msgid "hidden" +msgstr "прихований" + +#. line 41 +#. line 27 +#. line 41 +#. line 27 +#. line 41 +#. line 27 +#. line 41 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:117 +#: ../../../../templates/cache/f9/e9/d592e3c89e2f76520cf989aa8359d3d143d8fa4996ff1d97b3be51f87a05.php:92 +msgid "Message" +msgstr "Повідомлення" + +#. line 46 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:133 +msgid "public; attached to post" +msgstr "" + +#. line 58 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:150 +msgid "Length" +msgstr "Довжина" + +#. line 88 +#: ../../../../templates/cache/88/92/8e730a689121629afa3d2c0f374e1f246baa76e7cf0f3ec680f51805eccd.php:212 +msgid "New Ban" +msgstr "Нове блокування" + +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#. line 2 +#. line 5 +#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:25 +#: ../../../../templates/cache/cb/8b/63013711213735996df92becb7bd43d753c51314cfe5433c562706333eb0.php:31 +msgid "Phrase:" +msgstr "Фраза:" + +#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:38 +msgid "Posts" +msgstr "Дописи" + +#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:49 +msgid "IP address notes" +msgstr "Примітки до IP адреси" + +#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:62 +msgid "Bans" +msgstr "Блокування" + +#. line 18 +#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:88 +msgid "" +"(Search is case-insensitive and based on keywords. To match exact phrases, " +"use \"quotes\". Use an asterisk (*) for wildcard.)" +msgstr "" +"(Пошук ведеться за ключовими словами і є нечутливим до регістру. Щоб знайти " +"конкретні фрази, використовуйте \"лапки\". Для масок використовуйте зірочку " +"(*)." + +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:25 +msgid "There are no active bans." +msgstr "Немає чинних блокувань." + +#. line 8 +#. line 47 +#. line 8 +#. line 47 +#. line 8 +#. line 47 +#. line 8 +#. line 47 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:39 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:137 +msgid "IP address/mask" +msgstr "IP адреса/маска" + +#. line 12 +#. line 51 +#. line 12 +#. line 51 +#. line 12 +#. line 51 +#. line 12 +#. line 51 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:55 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:153 +msgid "Duration" +msgstr "Термін" + +#. line 92 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:269 +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:68 +msgid "Unban selected" +msgstr "Розблокувати вибраних" + +#. line 6 +#. line 4 +#. line 6 +#. line 4 +#. line 11 +#. line 6 +#. line 4 +#. line 6 +#. line 11 +#. line 6 +#. line 4 +#. line 11 +#. line 6 +#. line 4 +#. line 11 +#. line 6 +#. line 4 +#: ../../../../templates/cache/00/31/a027d7b6d57819b6e43e58620f3f4c76194dd75db65fc888a5053ce62803.php:34 +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:30 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:45 +msgid "Username" +msgstr "Імʼя користувача" + +#. line 23 +#: ../../../../templates/cache/00/31/a027d7b6d57819b6e43e58620f3f4c76194dd75db65fc888a5053ce62803.php:60 +msgid "Continue" +msgstr "Продовжити" + +#. line 80 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:210 +msgid "Appeal time" +msgstr "Час для оскарження" + +#. line 84 +#: ../../../../templates/cache/03/13/62c259daae13f7b39b689162b7cd380b2673bee7e05b90f0d34b69a01190.php:220 +msgid "Appeal reason" +msgstr "Причина оскарження" + +#: ../../../../templates/cache/7d/63/b6fd83bf4ed7f6031a2b3373b997d2d40617bf98899fe672a0aae48520c5.php:31 +msgid "There are no reports." +msgstr "Немає скарг." + +#: ../../../../post.php:802 ../../../../post.php:811 ../../../../post.php:825 +#: ../../../../post.php:894 ../../../../post.php:896 +msgid "That ban doesn't exist or is not for you." +msgstr "Це блокування не існує або його застосовано не до вас." + +#: ../../../../post.php:806 ../../../../post.php:815 ../../../../post.php:829 +#: ../../../../post.php:898 ../../../../post.php:900 +msgid "You cannot appeal a ban of this length." +msgstr "Ви не можете оскаржити блокування з таким терміном." + +#: ../../../../post.php:813 ../../../../post.php:822 ../../../../post.php:836 +#: ../../../../post.php:905 ../../../../post.php:907 +msgid "You cannot appeal this ban again." +msgstr "Ви не можете оскаржити це блокування знову." + +#: ../../../../post.php:818 ../../../../post.php:827 ../../../../post.php:841 +#: ../../../../post.php:910 ../../../../post.php:912 +msgid "There is already a pending appeal for this ban." +msgstr "Оскарження цього блокування вже розглядається." + +#: ../../../../inc/image.php:24 ../../../../inc/image.php:62 +msgid "Unsupported file format: " +msgstr "Непідтримуваний формат файла:" + +#: ../../../../inc/image.php:282 ../../../../inc/image.php:288 +msgid "Failed to redraw image!" +msgstr "Не вдалося перемалювати зображення!" + +#: ../../../../inc/image.php:324 ../../../../inc/image.php:343 +#: ../../../../inc/image.php:368 ../../../../inc/image.php:342 +#: ../../../../inc/image.php:366 ../../../../inc/image.php:372 +msgid "Failed to resize image!" +msgstr "Не вдалося змінити розмір зображення!" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:35 +msgid "You were banned! ;_;" +msgstr "Вас було заблоковано! ;_;" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:41 +msgid "You are banned! ;_;" +msgstr "Вас заблоковано! ;_;" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:52 +msgid "You were banned from" +msgstr "Вас було заблоковано на" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:58 +msgid "You have been banned from" +msgstr "Вас заблоковано на" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:82 +msgid "for the following reason:" +msgstr "з такої причини:" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:88 +msgid "for an unspecified reason." +msgstr ", причину не вказано." + +#. line 32 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:110 +msgid "Your ban was filed on" +msgstr "" + +#. line 51 +#. line 59 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:123 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:156 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:148 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:181 +msgid "has since expired. Refresh the page to continue." +msgstr "сплив. Оновіть сторінку, аби продовжити." + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:129 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:154 +msgid "expires" +msgstr "спливає" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:133 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:158 +msgid "from now, which is on" +msgstr "" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:183 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:208 +msgid "will not expire" +msgstr "не сплине" + +#. line 78 +#. line 86 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:192 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:217 +msgid "Your IP address is" +msgstr "Ваша IP адреса" + +#. line 86 +#. line 94 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:215 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:240 +msgid "You were banned for the following post on" +msgstr "Вас було заблоковано за цей допис" + +#. line 95 +#. line 103 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:239 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:240 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:265 +msgid "You submitted an appeal for this ban on" +msgstr "Ви подали оскарження цього блокування" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:245 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:246 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:271 +msgid "It is still pending" +msgstr "Воно досі розглядається" + +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 101 +#. line 112 +#. line 109 +#. line 120 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:257 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:289 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:258 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:290 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:283 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:315 +msgid "You appealed this ban on" +msgstr "Ви оскаржили це блокування" + +#. line 103 +#. line 111 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:265 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:266 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:291 +msgid "and it was denied. You may not appeal this ban again." +msgstr ". Оскарження відхилено. Ви не можете знову оскаржувати це блокування." + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:272 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:273 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:298 +msgid "" +"You have submitted the maximum number of ban appeals allowed. You may not " +"appeal this ban again." +msgstr "" +"Ви подали максимальну кількість оскаржень, і більше не можете оскаржувати це " +"блокування." + +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 114 +#. line 121 +#. line 122 +#. line 129 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:297 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:318 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:298 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:319 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:323 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:344 +msgid "and it was denied." +msgstr ". Оскарження відхилено." + +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 116 +#. line 123 +#. line 124 +#. line 131 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:302 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:323 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:303 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:324 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:328 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:349 +msgid "You may appeal this ban again. Please enter your reasoning below." +msgstr "" +"Ви можете оскаржити це блокування ще раз. Будь ласка, обґрунтуйте його " +"несправедливість тут." + +#. line 119 +#. line 127 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:310 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:311 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:336 +msgid "You last appealed this ban on" +msgstr "Востаннє ви оскаржували це блокування" + +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:332 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:333 +#: ../../../../templates/cache/b7/ae/f9663c9ca58d1e218de29e04d0fa229f6263f4e68b613ce608bc023897a2.php:358 +msgid "You may appeal this ban. Please enter your reasoning below." +msgstr "" +"Ви можете оскаржити це блокування. Будь ласка, обґрунтуйте його " +"несправедливість тут." + +#. line 4 +#. line 16 +#. line 134 +#. line 93 +#. line 4 +#. line 16 +#. line 134 +#. line 93 +#. line 4 +#. line 16 +#. line 134 +#. line 93 +#. line 4 +#. line 16 +#. line 134 +#. line 93 +#. line 4 +#. line 16 +#. line 134 +#: ../../../../templates/cache/f8/05/d647b76d6ba28842b313895b0d435295026f1912dc49639bd64e3b42eba3.php:30 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:55 +#: ../../../../templates/cache/6a/a4/b13523024ba2660a4f8f05e0c909c55477f675db9a2620075762ac245c91.php:375 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:255 +msgid "IP address" +msgstr "IP адреса" + +#. line 3 +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:26 +msgid "ID" +msgstr "ID" + +#. line 5 +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:34 +msgid "Type" +msgstr "Тип" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:45 +msgid "Last action" +msgstr "Остання дія" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:80 +msgid "Unknown" +msgstr "Невідомо" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:94 +msgid "none" +msgstr "немає" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:174 +msgid "Promote" +msgstr "Підвищити" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:187 +msgid "Demote" +msgstr "Понизити" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:191 +msgid "Are you sure you want to demote yourself?" +msgstr "Ви впевнені, що хочете себе понизити" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:204 +msgid "log" +msgstr "журнал" + +#: ../../../../templates/cache/fc/8d/2b5f6c25d93a9966c429a79ee7ebdd921957079dab214aebbc665d67b9f4.php:226 +msgid "PM" +msgstr "Приватне повідомлення" + +#. line 6 +#: ../../../../templates/cache/32/3a/d7e02cef5846ec4f1f423bb0ad2d3c307845d29f70da3f8a90a41f873e7d.php:36 +msgid "Thread ID" +msgstr "ID нитки" + +#. line 14 +#: ../../../../templates/cache/32/3a/d7e02cef5846ec4f1f423bb0ad2d3c307845d29f70da3f8a90a41f873e7d.php:51 +msgid "Leave shadow thread" +msgstr "" + +#. line 18 +#: ../../../../templates/cache/32/3a/d7e02cef5846ec4f1f423bb0ad2d3c307845d29f70da3f8a90a41f873e7d.php:58 +msgid "locks thread; replies to it with a link." +msgstr "закриває нитку, відповідає туди посиланням." + +#. line 22 +#. line 13 +#. line 22 +#. line 13 +#. line 22 +#. line 13 +#: ../../../../templates/cache/32/3a/d7e02cef5846ec4f1f423bb0ad2d3c307845d29f70da3f8a90a41f873e7d.php:65 +#: ../../../../templates/cache/56/25/ac2c51fa6b3e26f9f9ed7dda5224acfbec96881d648c8ded10c5eef2c3e5.php:50 +msgid "Target board" +msgstr "Дошка призначення" + +#. line 8 +#: ../../../../templates/cache/cb/8b/63013711213735996df92becb7bd43d753c51314cfe5433c562706333eb0.php:40 +msgid "Select board" +msgstr "Вибрати дошку" + +#. line 17 +#: ../../../../templates/cache/cb/8b/63013711213735996df92becb7bd43d753c51314cfe5433c562706333eb0.php:73 +msgid "" +"Search is case-insensitive and based on keywords. To match exact phrases, " +"use \"quotes\". Use an asterisk (*) for wildcard.

You may apply the following filters to your searches: " +"id, thread, subject, and " +"name. To apply a filter, simply add to your query, for " +"example, name:Anonymous or subject:\"Some Thread\". " +"Wildcards cannot be used in filters." +msgstr "" +"(Пошук ведеться за ключовими словами і є нечутливим до регістру. Щоб знайти " +"конкретні фрази, використовуйте \"лапки\". Для масок використовуйте зірочку " +"(*).

Ви можете застосувати до свого " +"пошуку такі фільтри: id, нитка, " +"тема та імʼя. Щоб застосувати фільтр, " +"просто додайте його до запиту, наприклад, імʼя:Хтось або тема:" +"\"Якась нитка\". У фільтрах не можна використовувати маску." + +#. line 2 +#: ../../../../templates/cache/57/a7/c0a734e494c78acfc595f033a070bdc87fdc3e6a28ad5aaa8666c7a1a966.php:25 +msgid "Are you sure you want to do that?" +msgstr "Ви впевнені, що хочете це зробити?" + +#: ../../../../templates/cache/57/a7/c0a734e494c78acfc595f033a070bdc87fdc3e6a28ad5aaa8666c7a1a966.php:31 +msgid "Click to proceed to" +msgstr "Клацніть, щоб перейти до" + +#. line 5 +#: ../../../../templates/cache/57/a7/c0a734e494c78acfc595f033a070bdc87fdc3e6a28ad5aaa8666c7a1a966.php:39 +msgid "" +"You are probably seeing this message because Javascript being disabled. This " +"is a necessary security measure to prevent CSRF attacks." +msgstr "" +"Найімовірніше, ви бачите це повідомлення тому, що Javascript вимкнено. Його " +"повинно бути ввімкнено для захисту від міжсайтової підробки запиту." + +#. line 7 +#: ../../../../templates/cache/3a/62/f804928dbcf285e3d5d8d7ae31b1e3a7c78264f270efa9650d31f69c7897.php:44 +msgid "Report date" +msgstr "Дата скарги" + +#: ../../../../templates/cache/3a/62/f804928dbcf285e3d5d8d7ae31b1e3a7c78264f270efa9650d31f69c7897.php:54 +msgid "Reported by" +msgstr "Хто поскаржився" + +#: ../../../../templates/cache/3a/62/f804928dbcf285e3d5d8d7ae31b1e3a7c78264f270efa9650d31f69c7897.php:73 +msgid "Discard abuse report" +msgstr "Відхилити скарги на порушення правил" + +#: ../../../../templates/cache/3a/62/f804928dbcf285e3d5d8d7ae31b1e3a7c78264f270efa9650d31f69c7897.php:93 +msgid "Discard all abuse reports by this IP address" +msgstr "Відхилити всі скарги на порушення правил від цієї IP адреси" + +#. line 4 +#: ../../../../templates/cache/f9/e9/d592e3c89e2f76520cf989aa8359d3d143d8fa4996ff1d97b3be51f87a05.php:27 +msgid "From" +msgstr "Від" + +#. line 34 +#: ../../../../templates/cache/f9/e9/d592e3c89e2f76520cf989aa8359d3d143d8fa4996ff1d97b3be51f87a05.php:105 +msgid "Delete forever" +msgstr "Видалити назавжди" + +#. line 39 +#: ../../../../templates/cache/f9/e9/d592e3c89e2f76520cf989aa8359d3d143d8fa4996ff1d97b3be51f87a05.php:119 +msgid "Reply with quote" +msgstr "Відповісти цитатою" + +#. line 18 +#: ../../../../templates/cache/1f/f5/c63468797b4f93a8005563716a720117a6d51a804f2124a4c5158ca78525.php:62 +msgid "Send message" +msgstr "Надіслати повідомлення" + +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:25 +msgid "There are no themes available." +msgstr "Немає доступних тем." + +#. line 11 +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:50 +msgid "Version" +msgstr "Версія" + +#. line 15 +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:60 +msgid "Description" +msgstr "Опис" + +#. line 19 +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:70 +msgid "Thumbnail" +msgstr "Мініатюра" + +#. line 27 +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:93 +msgid "Use theme" +msgstr "Використовувати тему" + +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:100 +msgid "Reconfigure" +msgstr "Переконфігурувати" + +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:102 +msgid "Install" +msgstr "Встановити" + +#: ../../../../templates/cache/ae/30/5b1888bb2e8ab6981af945fea88c1ecb021b0dfa8a068ee7adeb9dd3ee7d.php:123 +msgid "Uninstall" +msgstr "Видалити" + +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:72 +msgid "new; optional" +msgstr "нова; необовʼязкова" + +#. line 32 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:98 +msgid "Group" +msgstr "Група" + +#. line 56 +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:161 +msgid "All boards" +msgstr "Всі дошки" + +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:223 +msgid "Create user" +msgstr "Створити користувача" + +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:229 +msgid "Save changes" +msgstr "Зберегти зміни" + +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:236 +msgid "Delete user" +msgstr "Видалити користувача" + +#: ../../../../templates/cache/37/ea/10898251a344348e062662ce7a7b7f6c8dae001e2c860ce58ea35cedd935.php:331 +msgid "View more logs for this user." +msgstr "Переглянути більше записів журналу щодо цього користувача." + +#. line 84 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:255 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:266 +msgid "Flag" +msgstr "Позначити" + +#. line 87 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:261 +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:272 +msgid "None" +msgstr "Немає" + +#. When moving a thread to another board and choosing to keep a "shadow thread", an automated post (with +#. a capcode) will be made, linking to the new location for the thread. "%s" will be replaced with a +#. standard cross-board post citation (>>>/board/xxx) +#: ../../../../inc/config.php:1211 ../../../../inc/config.php:1220 +#: ../../../../inc/config.php:1251 ../../../../inc/config.php:1255 +#: ../../../../inc/config.php:1258 +#, php-format +msgid "Moved to %s." +msgstr "Переміщено до %s." + +#: ../../../../templates/themes/recent/theme.php:50 +msgid "" +"Can't build the RecentPosts theme, because there are no boards to be fetched." +msgstr "" +"Неможливо зібрати тему RecentPosts, тому що немає з яких дошок їх зібрати." + +#: ../../../../inc/config.php:997 ../../../../inc/config.php:1023 +#: ../../../../inc/config.php:1026 ../../../../inc/config.php:1029 +msgid "You have attempted to upload too many images!" +msgstr "Ви спробували завантажити надто багато зображень!" + +#. line 7 +#: ../../../../templates/cache/b9/2b/ba2b45df5e1d76f6cdfb98a47468df19a1ffc1c2af2dc1792eb75eeb0791.php:36 +msgid "Spoiler file" +msgstr "Заховати файл під спойлер" + +#. line 26 +#. line 35 +#. line 26 +#. line 35 +#. line 20 +#. line 28 +#. line 20 +#. line 28 +#. line 20 +#. line 28 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:80 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:107 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:67 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:90 +msgid "Bump order" +msgstr "порядком підіймання" + +#. line 27 +#. line 36 +#. line 27 +#. line 36 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:84 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:111 +msgid "Last reply" +msgstr "Остання відповідь" + +#. line 28 +#. line 37 +#. line 28 +#. line 37 +#. line 21 +#. line 29 +#. line 21 +#. line 29 +#. line 21 +#. line 29 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:88 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:115 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:71 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:94 +msgid "Creation date" +msgstr "датою створення" + +#. line 29 +#. line 38 +#. line 29 +#. line 38 +#. line 22 +#. line 30 +#. line 22 +#. line 30 +#. line 22 +#. line 30 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:92 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:119 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:75 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:98 +msgid "Reply count" +msgstr "кількістю відповідей" + +#. line 30 +#. line 39 +#. line 30 +#. line 39 +#. line 23 +#. line 31 +#. line 23 +#. line 31 +#. line 23 +#. line 31 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:96 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:123 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:79 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:102 +msgid "Random" +msgstr "довільно" + +#. line 33 +#. line 26 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:102 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:85 +msgid "Sort by" +msgstr "Сортувати за" + +#. line 42 +#. line 34 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:129 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:108 +msgid "Image size" +msgstr "Розмір зображення" + +#. line 44 +#. line 37 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:134 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:117 +msgid "Small" +msgstr "Маленьке" + +#. line 45 +#. line 38 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:138 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:121 +msgid "Large" +msgstr "Велике" + +#. line 6 +#: ../../../../templates/cache/56/25/ac2c51fa6b3e26f9f9ed7dda5224acfbec96881d648c8ded10c5eef2c3e5.php:36 +msgid "Post ID" +msgstr "ID допису" + +#. line 29 +#: ../../../../templates/cache/56/25/ac2c51fa6b3e26f9f9ed7dda5224acfbec96881d648c8ded10c5eef2c3e5.php:96 +msgid "Target thread" +msgstr "Цільовий допис" + +#: ../../../../post.php:48 +msgid "Post deletion is not allowed!" +msgstr "Видалення допису не дозволено!" + +#: ../../../../post.php:385 +msgid "Unrecognized file size determination method." +msgstr "Невідомий метод визначення розміру файла." + +#: ../../../../post.php:519 +msgid "Invalid flag selection!" +msgstr "Неправильний вибір позначок!" + +#: ../../../../post.php:631 ../../../../post.php:633 +msgid "exiftool failed!" +msgstr "exiftool не спрацювала!" + +#: ../../../../post.php:641 ../../../../post.php:643 +msgid "Could not auto-orient image!" +msgstr "Не вдалося автоматично обернути зображення!" + +#: ../../../../post.php:695 ../../../../post.php:697 +msgid "Could not strip EXIF metadata!" +msgstr "Не вдалося очистити файл від даних EXIF!" + +#: ../../../../inc/config.php:1049 ../../../../inc/config.php:1052 +#: ../../../../inc/config.php:1053 ../../../../inc/config.php:1055 +#: ../../../../inc/config.php:1056 +msgid "There was a problem processing your webm." +msgstr "Під час обробки вашої webm сталася помилка." + +#. Is this error used anywhere ? +#: ../../../../inc/config.php:1050 ../../../../inc/config.php:1054 +#: ../../../../inc/config.php:1057 +msgid "Invalid webm uploaded." +msgstr "Завантажено хибну webm." + +#: ../../../../inc/config.php:1051 ../../../../inc/config.php:1055 +#: ../../../../inc/config.php:1058 +msgid "" +"The uploaded webm contains an audio or another type of additional stream." +msgstr "Завантажена webm містить аудіо або ще один потік даних іншого типу." + +#: ../../../../inc/config.php:1052 ../../../../inc/config.php:1056 +#: ../../../../inc/config.php:1059 +msgid "The uploaded webm is longer than " +msgstr "Завантажена webm довша, ніж" + +#. line 48 +#: ../../../../templates/cache/b8/d9/05d4f2709538c393e80cdee33c24efe8d6fd13e68df2f5b3493356ef42e3.php:190 +msgid "Go to top" +msgstr "На початок сторінки" + +#: ../../../../templates/cache/cf/63/151e140d85df674832f4ede3f3e7811b97d4efa91cac6086ca7e8ce65d25.php:80 +msgid "Don't show my flag" +msgstr "Не показувати мої позначки" + +#. line 36 +#: ../../../../templates/cache/41/57/9143de5f74d921965e5ff24e0f1ce44a18317fd4937f5d8d65f56337acf3.php:113 +msgid "Very small" +msgstr "Дуже маленький" + +#: ../../../../templates/themes/recent/theme.php:99 +msgid "(no comment)" +msgstr "(коментар відсутній)" + +#: ../../../../templates/cache/3a/fa/a15b273b182a2c833d42475af728873be4a7cb0481854a52be6ed9380ea2.php:30 +msgid "There are no active posts." +msgstr "Немає активних дописів." + +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:49 +msgid "Show only bans from boards I moderate" +msgstr "Показати блокування тільки з тих дошок, які я модерую" + +#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:55 +msgid "Show only active bans" +msgstr "Показати тільки чинні блокування" diff --git a/inc/lock.php b/inc/lock.php new file mode 100644 index 00000000..4fb2f5df --- /dev/null +++ b/inc/lock.php @@ -0,0 +1,39 @@ +f = fopen("tmp/locks/$key", "w"); + } + } + + // Get a shared lock + function get($nonblock = false) { global $config; + if ($config['lock']['enabled'] == 'fs') { + $wouldblock = false; + flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) return false; + } + return $this; + } + + // Get an exclusive lock + function get_ex($nonblock = false) { global $config; + if ($config['lock']['enabled'] == 'fs') { + $wouldblock = false; + flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) return false; + } + return $this; + } + + // Free a lock + function free() { global $config; + if ($config['lock']['enabled'] == 'fs') { + flock($this->f, LOCK_UN); + } + return $this; + } +} diff --git a/inc/mod/auth.php b/inc/mod/auth.php index fa1a0f4f..42f34196 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -76,20 +76,20 @@ function generate_salt() { function login($username, $password) { global $mod, $config; - $query = prepare("SELECT `id`, `type`, `boards`, `password`, `salt` FROM ``mods`` WHERE `username` = :username"); + $query = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE BINARY `username` = :username"); $query->bindValue(':username', $username); $query->execute() or error(db_error($query)); if ($user = $query->fetch(PDO::FETCH_ASSOC)) { - list($version, $ok) = test_password($user['password'], $user['salt'], $password); + list($version, $ok) = test_password($user['password'], $user['version'], $password); if ($ok) { if ($config['password_crypt_version'] > $version) { // It's time to upgrade the password hashing method! - list ($user['salt'], $user['password']) = crypt_password($password); - $query = prepare("UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id"); + list ($user['version'], $user['password']) = crypt_password($password); + $query = prepare("UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id"); $query->bindValue(':password', $user['password']); - $query->bindValue(':salt', $user['salt']); + $query->bindValue(':version', $user['version']); $query->bindValue(':id', $user['id']); $query->execute() or error(db_error($query)); } @@ -130,7 +130,7 @@ function destroyCookies() { function modLog($action, $_board=null) { global $mod, $board, $config; $query = prepare("INSERT INTO ``modlogs`` VALUES (:id, :ip, :board, :time, :text)"); - $query->bindValue(':id', $mod['id'], PDO::PARAM_INT); + $query->bindValue(':id', (isset($mod['id']) ? $mod['id'] : -1), PDO::PARAM_INT); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->bindValue(':time', time(), PDO::PARAM_INT); $query->bindValue(':text', $action); @@ -146,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; @@ -212,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']) + ); + } +} diff --git a/inc/mod/pages.php b/inc/mod/pages.php index a07de4c7..e51bbd7d 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -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), @@ -608,7 +608,7 @@ function mod_news($page_no = 1) { rebuildThemes('news'); - header('Location: ?/news#' . $pdo->lastInsertId(), true, $config['redirect_http']); + header('Location: ?/edit_news#' . $pdo->lastInsertId(), true, $config['redirect_http']); } $query = prepare("SELECT * FROM ``news`` ORDER BY `id` DESC LIMIT :offset, :limit"); @@ -621,14 +621,14 @@ function mod_news($page_no = 1) { error($config['error']['404']); foreach ($news as &$entry) { - $entry['delete_token'] = make_secure_link_token('news/delete/' . $entry['id']); + $entry['delete_token'] = make_secure_link_token('edit_news/delete/' . $entry['id']); } $query = prepare("SELECT COUNT(*) FROM ``news``"); $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('news'))); + mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('edit_news'))); } function mod_news_delete($id) { @@ -643,7 +643,7 @@ function mod_news_delete($id) { modLog('Deleted a news entry'); - header('Location: ?/news', true, $config['redirect_http']); + header('Location: ?/edit_news', true, $config['redirect_http']); } function mod_log($page_no = 1) { @@ -698,6 +698,42 @@ function mod_user_log($username, $page_no = 1) { mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'username' => $username)); } +function mod_board_log($board, $page_no = 1, $hide_names = false, $public = false) { + global $config; + + if ($page_no < 1) + error($config['error']['404']); + + if (!hasPermission($config['mod']['mod_board_log'], $board) && !$public) + error($config['error']['noaccess']); + + $query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board ORDER BY `time` DESC LIMIT :offset, :limit"); + $query->bindValue(':board', $board); + $query->bindValue(':limit', $config['mod']['modlog_page'], PDO::PARAM_INT); + $query->bindValue(':offset', ($page_no - 1) * $config['mod']['modlog_page'], PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + $logs = $query->fetchAll(PDO::FETCH_ASSOC); + + if (empty($logs) && $page_no > 1) + error($config['error']['404']); + + if (!hasPermission($config['mod']['show_ip'])) { + // Supports ipv4 only! + foreach ($logs as $i => &$log) { + $log['text'] = preg_replace_callback('/(?:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:<\/a>)?/', function($matches) { + return "xxxx";//less_ip($matches[1]); + }, $log['text']); + } + } + + $query = prepare("SELECT COUNT(*) FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `board` = :board"); + $query->bindValue(':board', $board); + $query->execute() or error(db_error($query)); + $count = $query->fetchColumn(); + + mod_page(_('Board log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'board' => $board, 'hide_names' => $hide_names, 'public' => $public)); +} + function mod_view_board($boardName, $page_no = 1) { global $config, $mod; @@ -846,7 +882,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() { @@ -1053,6 +1089,28 @@ function mod_sticky($board, $unsticky, $post) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } +function mod_cycle($board, $uncycle, $post) { + global $config; + + if (!openBoard($board)) + error($config['error']['noboard']); + + if (!hasPermission($config['mod']['cycle'], $board)) + error($config['error']['noaccess']); + + $query = prepare(sprintf('UPDATE ``posts_%s`` SET `cycle` = :cycle WHERE `id` = :id AND `thread` IS NULL', $board)); + $query->bindValue(':id', $post); + $query->bindValue(':cycle', $uncycle ? 0 : 1); + $query->execute() or error(db_error($query)); + if ($query->rowCount()) { + modLog(($uncycle ? 'Made not cyclical' : 'Made cyclical') . " thread #{$post}"); + buildThread($post); + buildIndex(); + } + + header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); +} + function mod_bumplock($board, $unbumplock, $post) { global $config; @@ -1256,7 +1314,9 @@ function mod_move($originBoard, $postID) { $post['has_file'] = true; foreach ($post['files'] as $i => &$file) { $file['file_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['img'] . $file['file']; - $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']; + + if (isset($file['thumb'])) + $file['thumb_path'] = sprintf($config['board_path'], $board['uri']) . $config['dir']['thumb'] . $file['thumb']; } } else { $post['has_file'] = false; @@ -1473,6 +1533,15 @@ function mod_edit_post($board, $edit_raw_html, $postID) { error($config['error']['404']); if (isset($_POST['name'], $_POST['email'], $_POST['subject'], $_POST['body'])) { + // Remove any modifiers they may have put in + $_POST['body'] = remove_modifiers($_POST['body']); + + // Add back modifiers in the original post + $modifiers = extract_modifiers($post['body_nomarkup']); + foreach ($modifiers as $key => $value) { + $_POST['body'] .= "$value"; + } + if ($edit_raw_html) $query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup WHERE `id` = :id', $board)); else @@ -1501,15 +1570,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)); } } @@ -1649,6 +1723,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; @@ -1734,12 +1810,12 @@ function mod_user($uid) { } if ($_POST['password'] != '') { - list($salt, $password) = crypt_password($_POST['password']); + list($version, $password) = crypt_password($_POST['password']); - $query = prepare('UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id'); + $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id'); $query->bindValue(':id', $uid); $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); + $query->bindValue(':version', $version); $query->execute() or error(db_error($query)); modLog('Changed password for ' . utf8tohtml($_POST['username']) . ' (#' . $user['id'] . ')'); @@ -1760,12 +1836,12 @@ function mod_user($uid) { if (hasPermission($config['mod']['change_password']) && $uid == $mod['id'] && isset($_POST['password'])) { if ($_POST['password'] != '') { - list($salt, $password) = crypt_password($_POST['password']); + list($version, $password) = crypt_password($_POST['password']); - $query = prepare('UPDATE ``mods`` SET `password` = :password, `salt` = :salt WHERE `id` = :id'); + $query = prepare('UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id'); $query->bindValue(':id', $uid); $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); + $query->bindValue(':version', $version); $query->execute() or error(db_error($query)); modLog('Changed own password'); @@ -1832,12 +1908,12 @@ function mod_user_new() { if (!isset($config['mod']['groups'][$type]) || $type == DISABLED) error(sprintf($config['error']['invalidfield'], 'type')); - list($salt, $password) = crypt_password($_POST['password']); + list($version, $password) = crypt_password($_POST['password']); - $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :salt, :type, :boards)'); + $query = prepare('INSERT INTO ``mods`` VALUES (NULL, :username, :password, :version, :type, :boards)'); $query->bindValue(':username', $_POST['username']); $query->bindValue(':password', $password); - $query->bindValue(':salt', $salt); + $query->bindValue(':version', $version); $query->bindValue(':type', $type); $query->bindValue(':boards', implode(',', $boards)); $query->execute() or error(db_error($query)); @@ -2560,7 +2636,7 @@ function mod_theme_uninstall($theme_name) { // Clean cache Cache::delete("themes"); - Cache::delete("theme_settings_".$theme); + Cache::delete("theme_settings_".$theme_name); header('Location: ?/themes', true, $config['redirect_http']); } @@ -2578,6 +2654,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 = "

"; + $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; @@ -2694,3 +2931,4 @@ function mod_debug_apc() { mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars)); } + diff --git a/inc/nntpchan/nntpchan.php b/inc/nntpchan/nntpchan.php new file mode 100644 index 00000000..de67a193 --- /dev/null +++ b/inc/nntpchan/nntpchan.php @@ -0,0 +1,152 @@ +"; +} + + +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'] . " "; + + 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); +} diff --git a/inc/nntpchan/tests.php b/inc/nntpchan/tests.php new file mode 100644 index 00000000..a63789d7 --- /dev/null +++ b/inc/nntpchan/tests.php @@ -0,0 +1,30 @@ + "czaks ", "Message-Id" => "<1234.0000.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None"], +[['type' => 'text/plain', 'text' => "THIS IS A NEW TEST THREAD"]]); +echo "\n@@@@ Single msg:\n"; +echo $m1 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1234.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, with no image :("]]); +echo "\n@@@@ Single msg and pseudoimage:\n"; +echo $m2 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.2137.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, now with an image!"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"]]); +echo "\n@@@@ Single msg and two pseudoimages:\n"; +echo $m3 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1488.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], +[['type' => 'text/plain', 'text' => "hello world, now WITH TWO IMAGES!!!"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"], + ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif2.gif"]]); +shoveitup($m0, "<1234.0000.".$time."@example.vichan.net>"); +sleep(1); +shoveitup($m1, "<1234.1234.".$time."@example.vichan.net>"); +sleep(1); +shoveitup($m2, "<1234.2137.".$time."@example.vichan.net>"); +shoveitup($m3, "<1234.1488.".$time."@example.vichan.net>"); + diff --git a/inc/polyfill.php b/inc/polyfill.php new file mode 100644 index 00000000..ac40a00a --- /dev/null +++ b/inc/polyfill.php @@ -0,0 +1,28 @@ + $i ? $i : 0]) ^ ord($theirs[$i]); + } + + return $answer === 0 && $olen === $tlen; + } +} diff --git a/inc/queue.php b/inc/queue.php new file mode 100644 index 00000000..66305b3b --- /dev/null +++ b/inc/queue.php @@ -0,0 +1,49 @@ +lock = new Lock($key); + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + $this->key = "tmp/queue/$key/"; + } + } + + function push($str) { global $config; + if ($config['queue']['enabled'] == 'fs') { + $this->lock->get_ex(); + file_put_contents($this->key.microtime(true), $str); + $this->lock->free(); + } + return $this; + } + + function pop($n = 1) { global $config; + if ($config['queue']['enabled'] == 'fs') { + $this->lock->get_ex(); + $dir = opendir($this->key); + $paths = array(); + while ($n > 0) { + $path = readdir($dir); + if ($path === FALSE) break; + elseif ($path == '.' || $path == '..') continue; + else { $paths[] = $path; $n--; } + } + $out = array(); + foreach ($paths as $v) { + $out []= file_get_contents($this->key.$v); + unlink($this->key.$v); + } + $this->lock->free(); + return $out; + } + } +} + +// Don't use the constructor. Use the get_queue function. +$queues = array(); + +function get_queue($name) { global $queues; + return $queues[$name] = isset ($queues[$name]) ? $queues[$name] : new Queue($name); +} diff --git a/inc/route.php b/inc/route.php new file mode 100644 index 00000000..2a5c1732 --- /dev/null +++ b/inc/route.php @@ -0,0 +1,65 @@ + $fun) { + $id = '@^' . preg_quote($id, '@') . '$@u'; + + $id = str_replace('%b', '('.$config['board_regex'].')', $id); + $id = str_replace('%d', '([0-9]+)', $id); + $id = str_replace('%s', '[a-zA-Z0-9-]+', $id); + + $matches = null; + + if (preg_match ($id, $request, $matches)) { + array_shift($matches); + + $reached = array($fun, $matches); + + break; + } + } + + return $reached; +} + diff --git a/install.php b/install.php index 5a2d724a..ff994010 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ vichan upgrade path. query("CREATE TABLE IF NOT EXISTS ``search_queries`` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8;") or error(db_error()); @@ -579,6 +618,27 @@ if (file_exists($config['has_installed'])) { die(Element('page.html', $page)); } +function create_config_from_array(&$instance_config, &$array, $prefix = '') { + foreach ($array as $name => $value) { + if (is_array($value)) { + $instance_config .= "\n"; + create_config_from_array($instance_config, $value, $prefix . '[\'' . addslashes($name) . '\']'); + $instance_config .= "\n"; + } else { + $instance_config .= ' $config' . $prefix . '[\'' . addslashes($name) . '\'] = '; + + if (is_numeric($value)) + $instance_config .= $value; + else + $instance_config .= "'" . addslashes($value) . "'"; + + $instance_config .= ";\n"; + } + } +} + +session_start(); + if ($step == 0) { // Agreeement $page['body'] = ' @@ -612,7 +672,7 @@ if ($step == 0) { 'installed' => extension_loaded('pdo'), 'required' => true ), - 'PDO' => array( + 'GD' => array( 'installed' => extension_loaded('gd'), 'required' => true ), @@ -625,17 +685,17 @@ if ($step == 0) { $tests = array( array( 'category' => 'PHP', - 'name' => 'PHP ≥ 5.3', - 'result' => PHP_VERSION_ID >= 50300, + 'name' => 'PHP ≥ 5.4', + 'result' => PHP_VERSION_ID >= 50400, 'required' => true, - 'message' => 'vichan requires PHP 5.3 or better.', + 'message' => 'vichan requires PHP 5.4 or better.', ), array( 'category' => 'PHP', - 'name' => 'PHP ≥ 5.4', - 'result' => PHP_VERSION_ID >= 50400, + 'name' => 'PHP ≥ 5.6', + 'result' => PHP_VERSION_ID >= 50600, 'required' => false, - 'message' => 'vichan works best on PHP 5.4 or better.', + 'message' => 'vichan works best on PHP 5.6 or better.', ), array( 'category' => 'PHP', @@ -692,6 +752,7 @@ if ($step == 0) { 'result' => $can_exec && shell_exec('which convert'), 'required' => false, 'message' => '(Optional) `convert` was not found or executable; command-line ImageMagick image processing cannot be enabled.', + 'effect' => function (&$config) { $config['thumb_method'] = 'convert'; }, ), array( 'category' => 'Image processing', @@ -706,6 +767,7 @@ if ($step == 0) { 'result' => $can_exec && shell_exec('which gm'), 'required' => false, 'message' => '(Optional) `gm` was not found or executable; command-line GraphicsMagick (faster than ImageMagick) cannot be enabled.', + 'effect' => function (&$config) { $config['thumb_method'] = 'gm'; }, ), array( 'category' => 'Image processing', @@ -713,13 +775,25 @@ if ($step == 0) { 'result' => $can_exec && shell_exec('which gifsicle'), 'required' => false, 'message' => '(Optional) `gifsicle` was not found or executable; you may not use `convert+gifsicle` for better animated GIF thumbnailing.', + 'effect' => function (&$config) { if ($config['thumb_method'] == 'gm') $config['thumb_method'] = 'gm+gifsicle'; + if ($config['thumb_method'] == 'convert') $config['thumb_method'] = 'convert+gifsicle'; }, ), array( 'category' => 'Image processing', - 'name' => '`md5sum` (quick file hashing)', + 'name' => '`md5sum` (quick file hashing on GNU/Linux)', + 'prereq' => '', 'result' => $can_exec && shell_exec('echo "vichan" | md5sum') == "141225c362da02b5c359c45b665168de -\n", 'required' => false, - 'message' => '(Optional) `md5sum` was not found or executable; file hashing for multiple images will be slower.', + 'message' => '(Optional) `md5sum` was not found or executable; file hashing for multiple images will be slower. Ignore if not using Linux.', + 'effect' => function (&$config) { $config['gnu_md5'] = true; }, + ), + array( + 'category' => 'Image processing', + 'name' => '`/sbin/md5` (quick file hashing on BSDs)', + 'result' => $can_exec && shell_exec('echo "vichan" | /sbin/md5 -r') == "141225c362da02b5c359c45b665168de\n", + 'required' => false, + 'message' => '(Optional) `/sbin/md5` was not found or executable; file hashing for multiple images will be slower. Ignore if not using BSD.', + 'effect' => function (&$config) { $config['bsd_md5'] = true; }, ), array( 'category' => 'File permissions', @@ -735,6 +809,13 @@ if ($step == 0) { 'required' => true, 'message' => 'You must give vichan permission to create (and write to) the templates/cache directory or performance will be drastically reduced.' ), + array( + 'category' => 'File permissions', + 'name' => getcwd() . '/tmp/cache', + 'result' => is_dir('tmp/cache') && is_writable('tmp/cache'), + 'required' => true, + 'message' => 'You must give vichan permission to write to the tmp/cache directory.' + ), array( 'category' => 'File permissions', 'name' => getcwd() . '/inc/instance-config.php', @@ -758,17 +839,27 @@ if ($step == 0) { 'message' => 'vichan is still beta software and it\'s not going to come out of beta any time soon. As there are often many months between releases yet changes and bug fixes are very frequent, it\'s recommended to use the git repository to maintain your vichan installation. Using git makes upgrading much easier.' ) ); - + $config['font_awesome'] = true; + $additional_config = array(); + foreach ($tests as $test) { + if ($test['result'] && isset($test['effect'])) { + $test['effect']($additional_config); + } + } + $more = ''; + create_config_from_array($more, $additional_config); + $_SESSION['more'] = $more; + echo Element('page.html', array( 'body' => Element('installer/check-requirements.html', array( 'extensions' => $extensions, 'tests' => $tests, - 'config' => $config + 'config' => $config, )), 'title' => 'Checking environment', - 'config' => $config + 'config' => $config, )); } elseif ($step == 2) { // Basic config @@ -779,14 +870,18 @@ if ($step == 0) { echo Element('page.html', array( 'body' => Element('installer/config.html', array( - 'config' => $config + 'config' => $config, + 'more' => $_SESSION['more'], )), 'title' => 'Configuration', 'config' => $config )); } elseif ($step == 3) { + $more = $_POST['more']; + unset($_POST['more']); + $instance_config = -' $value) { - if (is_array($value)) { - $instance_config .= "\n"; - create_config_from_array($instance_config, $value, $prefix . '[\'' . addslashes($name) . '\']'); - $instance_config .= "\n"; - } else { - $instance_config .= ' $config' . $prefix . '[\'' . addslashes($name) . '\'] = '; - - if (is_numeric($value)) - $instance_config .= $value; - else - $instance_config .= "'" . addslashes($value) . "'"; - - $instance_config .= ";\n"; - } - } - } - create_config_from_array($instance_config, $_POST); + $instance_config .= "\n"; + $instance_config .= $more; $instance_config .= "\n"; if (@file_put_contents('inc/instance-config.php', $instance_config)) { diff --git a/install.sql b/install.sql index fbf220c1..ca60a58a 100644 --- a/install.sql +++ b/install.sql @@ -65,6 +65,7 @@ CREATE TABLE IF NOT EXISTS `boards` ( `uri` varchar(58) CHARACTER SET utf8 NOT NULL, `title` tinytext NOT NULL, `subtitle` tinytext, + -- `indexed` boolean default true, PRIMARY KEY (`uri`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; @@ -132,7 +133,7 @@ CREATE TABLE IF NOT EXISTS `mods` ( `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(30) NOT NULL, `password` varchar(256) CHARACTER SET ascii NOT NULL COMMENT 'SHA256', - `salt` varchar(64) CHARACTER SET ascii NOT NULL, + `version` varchar(64) CHARACTER SET ascii NOT NULL, `type` smallint(2) NOT NULL, `boards` text CHARACTER SET utf8 NOT NULL, PRIMARY KEY (`id`), @@ -244,7 +245,7 @@ CREATE TABLE IF NOT EXISTS `search_queries` ( `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, `query` text NOT NULL -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -- -------------------------------------------------------- @@ -296,6 +297,57 @@ CREATE TABLE IF NOT EXISTS `ban_appeals` ( KEY `ban_id` (`ban_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; +-- -------------------------------------------------------- + +-- +-- Table structure for table `pages` +-- + +CREATE TABLE IF NOT EXISTS `pages` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `board` varchar(58) CHARACTER SET utf8 DEFAULT NULL, + `name` varchar(255) CHARACTER SET utf8 NOT NULL, + `title` varchar(255) DEFAULT NULL, + `type` varchar(255) DEFAULT NULL, + `content` text, + PRIMARY KEY (`id`), + UNIQUE KEY `u_pages` (`name`,`board`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `nntp_references` +-- + +CREATE TABLE IF NOT EXISTS `nntp_references` ( + `board` varchar(30) NOT NULL, + `id` int(11) unsigned NOT NULL, + `message_id` varchar(255) CHARACTER SET ascii NOT NULL, + `message_id_digest` varchar(40) CHARACTER SET ascii NOT NULL, + `own` tinyint(1) NOT NULL, + `headers` text, + PRIMARY KEY (`message_id_digest`), + UNIQUE KEY `message_id` (`message_id`), + UNIQUE KEY `u_board_id` (`board`, `id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `captchas` +-- + +CREATE TABLE IF NOT EXISTS `captchas` ( + `cookie` VARCHAR(50), + `extra` VARCHAR(200), + `text` VARCHAR(255), + `created_at` INT(11), + PRIMARY KEY (`cookie`,`extra`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + + /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + diff --git a/js/auto-scroll.js b/js/auto-scroll.js new file mode 100644 index 00000000..b8343f5e --- /dev/null +++ b/js/auto-scroll.js @@ -0,0 +1,27 @@ +$('document').ready(function () { + var autoScroll = localStorage['autoScroll'] ? true : false; + if (window.Options && Options.get_tab('general')){ + Options.extend_tab('general',''); + $('#autoScroll').find('input').prop('checked', autoScroll); + } + $('#autoScroll').on('change', function() { + if(autoScroll) { + delete localStorage.autoScroll; + } else { + localStorage.autoScroll = true; + } + autoScroll =! autoScroll + if(active_page == 'thread') + $('input.auto-scroll').prop('checked', autoScroll); + }); + if (active_page == 'thread') { + $('span[id="updater"]').children('a').after(' ( Scroll to New posts)'); + $('input.auto-scroll').prop('checked', autoScroll); + $(document).on('new_post', function (e, post) { + if ($('input.auto-scroll').prop('checked')) + { + scrollTo(0, $(post).offset().top - window.innerHeight + $(post).outerHeight(true)); + } + }); + } +}); diff --git a/js/captcha.js b/js/captcha.js new file mode 100644 index 00000000..018588b7 --- /dev/null +++ b/js/captcha.js @@ -0,0 +1,43 @@ +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); }); + }); + }); +} \ No newline at end of file diff --git a/js/catalog-search.js b/js/catalog-search.js index 17a8edac..ff9af785 100644 --- a/js/catalog-search.js +++ b/js/catalog-search.js @@ -34,16 +34,16 @@ if (active_page == 'catalog') { } function searchToggle() { - var button = $('#catalog_search_button')[0]; + var button = $('#catalog_search_button'); - if (!button.dataset.expanded) { - button.dataset.expanded = '1'; - button.innerText = 'Close'; + if (!button.data('expanded')) { + button.data('expanded', '1'); + button.text('Close'); $('.catalog_search').append(' '); $('#search_field').focus(); } else { - delete button.dataset.expanded; - button.innerText = 'Search'; + button.removeData('expanded'); + button.text('Search'); $('.catalog_search').children().last().remove(); $('div[id="Grid"]>.mix').each(function () { $(this).css('display', 'inline-block'); }); } diff --git a/js/catalog.js b/js/catalog.js index 768845f6..9adaeaef 100644 --- a/js/catalog.js +++ b/js/catalog.js @@ -1,8 +1,16 @@ if (active_page == 'catalog') $(function(){ + if (localStorage.catalog !== undefined) { + var catalog = JSON.parse(localStorage.catalog); + } else { + var catalog = {}; + localStorage.catalog = JSON.stringify(catalog); + } $("#sort_by").change(function(){ var value = this.value; - $("#sort-"+value).trigger("click"); + $('#Grid').mixItUp('sort', (value == "random" ? value : "sticky:desc " + value)); + catalog.sort_by = value; + localStorage.catalog = JSON.stringify(catalog); }); $("#image_size").change(function(){ @@ -11,9 +19,30 @@ if (active_page == 'catalog') $(function(){ $(".grid-li").removeClass("grid-size-small"); $(".grid-li").removeClass("grid-size-large"); $(".grid-li").addClass("grid-size-"+value); + catalog.image_size = value; + localStorage.catalog = JSON.stringify(catalog); }); - $('#Grid').mixitup({ + $('#Grid').mixItUp({ + animation: { + enable: false + } }); + if (catalog.sort_by !== undefined) { + $('#sort_by').val(catalog.sort_by).trigger('change'); + } + if (catalog.image_size !== undefined) { + $('#image_size').val(catalog.image_size).trigger('change'); + } + + $('div.thread').on('click', function(e) { + if ($(this).css('overflow-y') === 'hidden') { + $(this).css('overflow-y', 'auto'); + $(this).css('width', '100%'); + } else { + $(this).css('overflow-y', 'hidden'); + $(this).css('width', 'auto'); + } + }); }); diff --git a/js/comment-toolbar.js b/js/comment-toolbar.js new file mode 100644 index 00000000..51aa9808 --- /dev/null +++ b/js/comment-toolbar.js @@ -0,0 +1,383 @@ +/* + * comment-toolbar.js + * - Adds a toolbar above the commenting area containing most of 8Chan's formatting options + * - Press Esc to close quick-reply window when it's in focus + * + * Usage: + * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/comment-toolbar.js'; + */ +if (active_page == 'thread' || active_page == 'index') { + var formatText = (function($){ + "use strict"; + var self = {}; + self.rules = { + spoiler: { + text: _('Spoiler'), + key: 's', + multiline: false, + exclusiveline: false, + prefix:'**', + suffix:'**' + }, + italics: { + text: _('Italics'), + key: 'i', + multiline: false, + exclusiveline: false, + prefix: "''", + suffix: "''" + }, + bold: { + text: _('Bold'), + key: 'b', + multiline: false, + exclusiveline: false, + prefix: "'''", + suffix: "'''" + }, + underline: { + text: _('Underline'), + key: 'u', + multiline: false, + exclusiveline: false, + prefix:'__', + suffix:'__' + }, + code: { + text: _('Code'), + key: 'f', + multiline: true, + exclusiveline: false, + prefix: '[code]', + suffix: '[/code]' + }, + strike: { + text: _('Strike'), + key: 'd', + multiline:false, + exclusiveline:false, + prefix:'~~', + suffix:'~~' + }, + heading: { + text: _('Heading'), + key: 'r', + multiline:false, + exclusiveline:true, + prefix:'==', + suffix:'==' + } + }; + + self.toolbar_wrap = function(node) { + var parent = $(node).parents('form[name="post"]'); + self.wrap(parent.find('#body')[0],'textarea[name="body"]', parent.find('.format-text > select')[0].value, false); + }; + + self.wrap = function(ref, target, option, expandedwrap) { + // clean and validate arguments + if (ref == null) return; + var settings = {multiline: false, exclusiveline: false, prefix:'', suffix: null}; + $.extend(settings,JSON.parse(localStorage.formatText_rules)[option]); + + // resolve targets into array of proper node elements + // yea, this is overly verbose, oh well. + var res = []; + if (target instanceof Array) { + for (var indexa in target) { + if (target.hasOwnProperty(indexa)) { + if (typeof target[indexa] == 'string') { + var nodes = $(target[indexa]); + for (var indexb in nodes) { + if (indexa.hasOwnProperty(indexb)) res.push(nodes[indexb]); + } + } else { + res.push(target[indexa]); + } + } + } + } else { + if (typeof target == 'string') { + var nodes = $(target); + for (var index in nodes) { + if (nodes.hasOwnProperty(index)) res.push(nodes[index]); + } + } else { + res.push(target); + } + } + target = res; + //record scroll top to restore it later. + var scrollTop = ref.scrollTop; + + //We will restore the selection later, so record the current selection + var selectionStart = ref.selectionStart; + var selectionEnd = ref.selectionEnd; + + var text = ref.value; + var before = text.substring(0, selectionStart); + var selected = text.substring(selectionStart, selectionEnd); + var after = text.substring(selectionEnd); + var whiteSpace = [" ","\t"]; + var breakSpace = ["\r","\n"]; + var cursor; + + // handles multiline selections on formatting that doesn't support spanning over multiple lines + if (!settings.multiline) selected = selected.replace(/(\r|\n|\r\n)/g,settings.suffix +"$1"+ settings.prefix); + + // handles formatting that requires it to be on it's own line OR if the user wishes to expand the wrap to the nearest linebreak + if (settings.exclusiveline || expandedwrap) { + // buffer the begining of the selection until a linebreak + cursor = before.length -1; + while (cursor >= 0 && breakSpace.indexOf(before.charAt(cursor)) == -1) { + cursor--; + } + selected = before.substring(cursor +1) + selected; + before = before.substring(0, cursor +1); + + // buffer the end of the selection until a linebreak + cursor = 0; + while (cursor < after.length && breakSpace.indexOf(after.charAt(cursor)) == -1) { + cursor++; + } + selected += after.substring(0, cursor); + after = after.substring(cursor); + } + + // set values + var res = before + settings.prefix + selected + settings.suffix + after; + $(target).val(res); + + // restore the selection area and scroll of the reference + ref.selectionEnd = before.length + settings.prefix.length + selected.length; + if (selectionStart === selectionEnd) { + ref.selectionStart = ref.selectionEnd; + } else { + ref.selectionStart = before.length + settings.prefix.length; + } + ref.scrollTop = scrollTop; + }; + + self.build_toolbars = function(){ + if (localStorage.formatText_toolbar == 'true'){ + // remove existing toolbars + if ($('.format-text').length > 0) $('.format-text').remove(); + + // Place toolbar above each textarea input + var name, options = '', rules = JSON.parse(localStorage.formatText_rules); + for (var index in rules) { + if (!rules.hasOwnProperty(index)) continue; + name = rules[index].text; + + //add hint if key exists + if (rules[index].key) { + name += ' (CTRL + '+ rules[index].key.toUpperCase() +')'; + } + options += ''; + } + $('[name="body"]').before('
Wrap
'); + $('body').append(''); + } + }; + + self.add_rule = function(rule, index){ + if (rule === undefined) rule = { + text: 'New Rule', + key: '', + multiline:false, + exclusiveline:false, + prefix:'', + suffix:'' + } + + // generate an id for the rule + if (index === undefined) { + var rules = JSON.parse(localStorage.formatText_rules); + while (rules[index] || index === undefined) { + index = '' + index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1); + index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1); + index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1); + } + } + if (window.Options && Options.get_tab('formatting')){ + var html = $('
').html('\ + \ + \ + \ + \ + \ + \ + \ + '); + + if ($('.format_rule').length > 0) { + $('.format_rule').last().after(html); + } else { + Options.extend_tab('formatting', html); + } + } + }; + + self.save_rules = function(){ + var rule, newrules = {}, rules = $('.format_rule'); + for (var index=0;rules[index];index++) { + rule = $(rules[index]); + newrules[rule.attr('name')] = { + text: rule.find('[name="text"]').val(), + key: rule.find('[name="key"]').val(), + prefix: rule.find('[name="prefix"]').val(), + suffix: rule.find('[name="suffix"]').val(), + multiline: rule.find('[name="multiline"]').is(':checked'), + exclusiveline: rule.find('[name="exclusiveline"]').is(':checked') + }; + } + localStorage.formatText_rules = JSON.stringify(newrules); + self.build_toolbars(); + }; + + self.reset_rules = function(to_default) { + $('.format_rule').remove(); + var rules; + if (to_default) rules = self.rules; + else rules = JSON.parse(localStorage.formatText_rules); + for (var index in rules){ + if (!rules.hasOwnProperty(index)) continue; + self.add_rule(rules[index], index); + } + }; + + // setup default rules for customizing + if (!localStorage.formatText_rules) localStorage.formatText_rules = JSON.stringify(self.rules); + + // setup code to be ran when page is ready (work around for main.js compilation). + $(document).ready(function(){ + // Add settings to Options panel general tab + if (window.Options && Options.get_tab('general')) { + var s1 = '#formatText_keybinds>input', s2 = '#formatText_toolbar>input', e = 'change'; + Options.extend_tab('general', '\ +
\ + Formatting Options\ + \ + \ +
\ + '); + } else { + var s1 = '#formatText_keybinds', s2 = '#formatText_toolbar', e = 'click'; + $('hr:first').before(''); + $('hr:first').before(''); + } + + // add the tab for customizing the format settings + if (window.Options && !Options.get_tab('formatting')) { + Options.add_tab('formatting', 'angle-right', _('Customize Formatting')); + Options.extend_tab('formatting', '\ + \ + '); + + // Data control row + Options.extend_tab('formatting', '\ + \ + \ + \ + \ + '); + + // Descriptor row + Options.extend_tab('formatting', '\ + Name\ + ML\ + EL\ + Prefix\ + Suffix\ + Key\ + '); + + // Rule rows + var rules = JSON.parse(localStorage.formatText_rules); + for (var index in rules){ + if (!rules.hasOwnProperty(index)) continue; + self.add_rule(rules[index], index); + } + } + + // setting for enabling formatting keybinds + $(s1).on(e, function(e) { + console.log('Keybind'); + if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') { + localStorage.formatText_keybinds = 'true'; + if (window.Options && Options.get_tab('general')) e.target.checked = true; + } else { + localStorage.formatText_keybinds = 'false'; + if (window.Options && Options.get_tab('general')) e.target.checked = false; + } + }); + + // setting for toolbar injection + $(s2).on(e, function(e) { + console.log('Toolbar'); + if (!localStorage.formatText_toolbar || localStorage.formatText_toolbar == 'false') { + localStorage.formatText_toolbar = 'true'; + if (window.Options && Options.get_tab('general')) e.target.checked = true; + formatText.build_toolbars(); + } else { + localStorage.formatText_toolbar = 'false'; + if (window.Options && Options.get_tab('general')) e.target.checked = false; + $('.format-text').remove(); + } + }); + + // make sure the tab settings are switch properly at loadup + if (window.Options && Options.get_tab('general')) { + if (localStorage.formatText_keybinds == 'true') $(s1)[0].checked = true; + else $(s1)[0].checked = false; + if (localStorage.formatText_toolbar == 'true') $(s2)[0].checked = true; + else $(s2)[0].checked = false; + } + + // Initial toolbar injection + formatText.build_toolbars(); + + //attach listener to so it also works on quick-reply box + $('body').on('keydown', '[name="body"]', function(e) { + if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') return; + var key = String.fromCharCode(e.which).toLowerCase(); + var rules = JSON.parse(localStorage.formatText_rules); + for (var index in rules) { + if (!rules.hasOwnProperty(index)) continue; + if (key === rules[index].key && e.ctrlKey) { + e.preventDefault(); + if (e.shiftKey) { + formatText.wrap(e.target, 'textarea[name="body"]', index, true); + } else { + formatText.wrap(e.target, 'textarea[name="body"]', index, false); + } + } + } + }); + + // Signal that comment-toolbar loading has completed. + $(document).trigger('formatText'); + }); + + return self; + })(jQuery); +} diff --git a/js/expand-all-images.js b/js/expand-all-images.js index 5545045c..c110f51c 100644 --- a/js/expand-all-images.js +++ b/js/expand-all-images.js @@ -23,7 +23,15 @@ onready(function(){ .text(_('Expand all images')) .click(function() { $('a img.post-image').each(function() { - if (!$(this).parent()[0].dataset.expanded) + // Don't expand YouTube embeds + if ($(this).parent().parent().hasClass('video-container')) + return; + + // or WEBM + if (/^\/player\.php\?/.test($(this).parent().attr('href'))) + return; + + if (!$(this).parent().data('expanded')) $(this).parent().click(); }); @@ -34,8 +42,8 @@ onready(function(){ $('div#shrink-all-images a') .text(_('Shrink all images')) .click(function(){ - $('a img.post-image').each(function() { - if ($(this).parent()[0].dataset.expanded) + $('a img.full-image').each(function() { + if ($(this).parent().data('expanded')) $(this).parent().click(); }); $(this).parent().remove(); diff --git a/js/expand-too-long.js b/js/expand-too-long.js index d6ea43af..bbadb5f1 100644 --- a/js/expand-too-long.js +++ b/js/expand-too-long.js @@ -17,7 +17,7 @@ $(function() { e.preventDefault(); var url = $(this).attr('href'); - var body = $(this).parent().parent(); + var body = $(this).parents('.body'); $.ajax({ url: url, diff --git a/js/favorites.js b/js/favorites.js index daf7b732..027dc243 100644 --- a/js/favorites.js +++ b/js/favorites.js @@ -50,7 +50,7 @@ function add_favorites() { $('.boardlist').append(boards); }; -if (active_page == 'thread' || active_page == 'index') { +if (active_page == 'thread' || active_page == 'index' || active_page == 'catalog' || active_page == 'ukko') { $(document).ready(function(){ var favorites = JSON.parse(localStorage.favorites); var is_board_favorite = ~$.inArray(board_name, favorites); diff --git a/js/file-selector.js b/js/file-selector.js index 207a5ae9..c2b5381b 100644 --- a/js/file-selector.js +++ b/js/file-selector.js @@ -1,8 +1,9 @@ /* - * file-selector.js - Add support for drag and drop file selection, and paste from clipbboard on supported browsers. + * file-selector.js - Add support for drag and drop file selection, and paste from clipboard on supported browsers. * * Usage: * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/ajax.js'; * $config['additional_javascript'][] = 'js/file-selector.js'; */ function init_file_selector(max_images) { diff --git a/js/fix-report-delete-submit.js b/js/fix-report-delete-submit.js index 73b6dd9c..f5cda9ad 100644 --- a/js/fix-report-delete-submit.js +++ b/js/fix-report-delete-submit.js @@ -1,26 +1,69 @@ /* * fix-report-delete-submit.js - * https://github.com/savetheinternet/Tinyboard/blob/master/js/fix-report-delete-submit.js - * - * Fixes a known bug regarding the delete/report submit buttons. - * - * Released under the MIT license - * Copyright (c) 2012 Michael Save * * Usage: * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/post-menu.js'; * $config['additional_javascript'][] = 'js/fix-report-delete-submit.js'; * */ -$(document).ready(function(){ - $('form[name="postcontrols"] div.delete input:not([type="checkbox"]):not([type="submit"]):not([type="hidden"])').keypress(function(e) { - if(e.which == 13) { +if (active_page == 'thread' || active_page == 'index' || active_page == 'ukko') { +$(document).on('menu_ready', function(){ +var Menu = window.Menu; + +if ($('#delete-fields #password').length) { + Menu.add_item("delete_post_menu", _("Delete post")); + Menu.add_item("delete_file_menu", _("Delete file")); + Menu.onclick(function(e, $buf) { + var ele = e.target.parentElement.parentElement; + var $ele = $(ele); + var threadId = $ele.parent().attr('id').replace('thread_', ''); + var postId = $ele.find('.post_no').not('[id]').text(); + var board_name = $ele.parent().data('board'); + + $buf.find('#delete_post_menu,#delete_file_menu').click(function(e) { e.preventDefault(); - $(this).next().click(); - return false; + $('#delete_'+postId).prop('checked', 'checked'); + + if ($(this).attr('id') === 'delete_file_menu') { + $('#delete_file').prop('checked', 'checked'); + } else { + $('#delete_file').prop('checked', ''); + } + $('input[type="hidden"][name="board"]').val(board_name); + $('input[name=delete][type=submit]').click(); + }); + }); +} + +Menu.add_item("report_menu", _("Report")); +//Menu.add_item("global_report_menu", _("Global report")); +Menu.onclick(function(e, $buf) { + var ele = e.target.parentElement.parentElement; + var $ele = $(ele); + var threadId = $ele.parent().attr('id').replace('thread_', ''); + var postId = $ele.find('.post_no').not('[id]').text(); + var board_name = $ele.parent().data('board'); + + $buf.find('#report_menu,#global_report_menu').click(function(e) { + if ($(this).attr('id') === "global_report_menu") { + var global = '&global'; + } else { + var global = ''; } - return true; + window.open(configRoot+'report.php?board='+board_name+'&post=delete_'+postId+global, "", (global?"width=600, height=575":"width=500, height=275")); }); }); +$(document).on('new_post', function(){ + $('input.delete').hide(); +}); +$('input.delete').hide(); +$('#post-moderation-fields').hide(); +}); + +if (typeof window.Menu !== "undefined") { + $(document).trigger('menu_ready'); +} +} diff --git a/js/forced-anon.js b/js/forced-anon.js index 11327ec9..26d0cb4f 100644 --- a/js/forced-anon.js +++ b/js/forced-anon.js @@ -18,10 +18,10 @@ if (active_page == 'ukko' || active_page == 'thread' || active_page == 'index' || (window.Options && Options.get_tab('general'))) $(document).ready(function() { var force_anon = function() { - if($(this).children('a.capcode').length == 0) { + if ($(this).children('a.capcode').length == 0) { var id = $(this).parent().children('a.post_no:eq(1)').text(); - if($(this).children('a.email').length != 0) + if ($(this).children('a.email').length != 0) var p = $(this).children('a.email'); else var p = $(this); @@ -29,7 +29,7 @@ $(document).ready(function() { old_info[id] = {'name': p.children('span.name').text(), 'trip': p.children('span.trip').text()}; p.children('span.name').text('Anonymous'); - if(p.children('span.trip').length != 0) + if (p.children('span.trip').length != 0) p.children('span.trip').text(''); } }; @@ -40,44 +40,60 @@ $(document).ready(function() { var disable_fa = function() { $('p.intro label').each(function() { - if($(this).children('a.capcode').length == 0) { + if ($(this).children('a.capcode').length == 0) { var id = $(this).parent().children('a.post_no:eq(1)').text(); if(old_info[id]) { - if($(this).children('a.email').length != 0) + if ($(this).children('a.email').length != 0) var p = $(this).children('a.email'); else var p = $(this); p.children('span.name').text(old_info[id]['name']); - if(p.children('span.trip').length != 0) + if (p.children('span.trip').length != 0) p.children('span.trip').text(old_info[id]['trip']); } } }); }; + var toggle_id = function() { + if (localStorage.hideids == 'true'){ + $(this).addClass('hidden'); + } else { + $(this).removeClass('hidden'); + } + }; + old_info = {}; forced_anon = localStorage['forcedanon'] ? true : false; - var selector, event; - if (window.Options && Options.get_tab('general')) { - selector = '#forced-anon'; - event = 'change'; - Options.extend_tab("general", ""); - } - else { - selector = '#forced-anon'; - event = 'click'; + if (window.Options && Options.get_tab('general')) { + var s1 = '#hide-ids', s2 = '#forced-anon', e = 'change'; + Options.extend_tab("general", ""); + Options.extend_tab("general", ""); + } + else { + var s1 = '#hide-ids', s2 = '#forced-anon', e = 'click'; + $('hr:first').before(''); $('hr:first').before(''); $('div#forced-anon a').text(_('Forced anonymity')+' (' + (forced_anon ? _('enabled') : _('disabled')) + ')'); - } + } + $(s1).on(e, function(e) { + if (!localStorage.hideids || localStorage.hideids == 'false') { + localStorage.hideids = 'true'; + if (window.Options && Options.get_tab('general')) e.target.checked = true; + } else { + localStorage.hideids = 'false'; + if (window.Options && Options.get_tab('general')) e.target.checked = false; + } + $('.poster_id').each(toggle_id); + }); - $(selector).on(event, function() { + $(s2).on(e, function() { forced_anon = !forced_anon; - - if(forced_anon) { + if (forced_anon) { $('div#forced-anon a').text(_('Forced anonymity')+' ('+_('enabled')+')'); localStorage.forcedanon = true; enable_fa(); @@ -86,21 +102,27 @@ $(document).ready(function() { delete localStorage.forcedanon; disable_fa(); } - return false; }); + // initial option setup on script load + if (localStorage.hideids == 'true'){ + if (window.Options && Options.get_tab('general')) $('#hide-ids>input').prop('checked',true); + $('.poster_id').each(toggle_id); + } + if(forced_anon) { enable_fa(); - - if (window.Options && Options.get_tab('general')) { - $('#toggle-locked-threads>input').prop('checked', true); - } + if (window.Options && Options.get_tab('general')) { + $('#toggle-locked-threads>input').prop('checked', true); + } } $(document).on('new_post', function(e, post) { - if(forced_anon) + if (forced_anon) $(post).find('p.intro label').each(force_anon); + if (localStorage.hideids == 'true') + $(post).find('.poster_id').each(toggle_id); }); }); diff --git a/js/hide-images.js b/js/hide-images.js index 323a1a1f..4fdad891 100644 --- a/js/hide-images.js +++ b/js/hide-images.js @@ -77,7 +77,7 @@ $(document).ready(function(){ $(this).hide().after(show_link); - if ($(img).parent()[0].dataset.expanded == 'true') { + if ($(img).parent().data('expanded') == 'true') { $(img).parent().click(); } diff --git a/js/hide-threads.js b/js/hide-threads.js index 175018ed..47417d39 100644 --- a/js/hide-threads.js +++ b/js/hide-threads.js @@ -36,7 +36,7 @@ $(document).ready(function(){ } } - var fields_to_hide = 'div.post,div.video-container,video,iframe,img:not(.unanimated),canvas,p.fileinfo,a.hide-thread-link,div.new-posts,br'; + var fields_to_hide = 'div.file,div.post,div.video-container,video,iframe,img:not(.unanimated),canvas,p.fileinfo,a.hide-thread-link,div.new-posts,br'; var do_hide_threads = function() { var id = $(this).children('p.intro').children('a.post_no:eq(1)').text(); diff --git a/js/id_colors.js b/js/id_colors.js index 17f8b4d9..55454476 100644 --- a/js/id_colors.js +++ b/js/id_colors.js @@ -1,18 +1,18 @@ if (active_page == 'thread' || active_page == 'index') { $(document).ready(function(){ if (window.Options && Options.get_tab('general')) { - selector = '#color-ids>input'; - event = 'change'; + var selector = '#color-ids>input'; + var e = 'change'; Options.extend_tab("general", ""); } else { - selector = '#color-ids'; - event = 'click'; + var selector = '#color-ids'; + var e = 'click'; $('hr:first').before('') } - $(selector).on(event, function() { + $(selector).on(e, function() { if (localStorage.color_ids === 'true') { localStorage.color_ids = 'false'; } else { @@ -50,12 +50,6 @@ if (active_page == 'thread' || active_page == 'index') { "border-radius": "8px", "color": ft }); - - $(el).mouseover(function() { - $(this).css('color', '#800000'); // how about a CSS :hover rule instead? - }).mouseout(function() { - $(this).css('color', ft); - }); } $(".poster_id").each(function(k, v){ diff --git a/js/image-hover.js b/js/image-hover.js index 93d61702..66e908d7 100644 --- a/js/image-hover.js +++ b/js/image-hover.js @@ -180,3 +180,4 @@ function imageHoverEnd() { //Pashe, WTFPL initImageHover(); }); } + diff --git a/js/infinite-scroll.js b/js/infinite-scroll.js index e212f9d6..b3c5c7ae 100644 --- a/js/infinite-scroll.js +++ b/js/infinite-scroll.js @@ -21,7 +21,7 @@ var activate = function() { if (document.location.hash != '#all') return false; $(window).on("scroll", function() { - scrolltest(); + scrolltest(); }); scrolltest(); @@ -30,53 +30,54 @@ var activate = function() { var scrolltest = function() { if ($(window).scrollTop() + $(window).height() + 1000 > $(document).height() && !loading) { - load_next_page(); + load_next_page(); } }; var load_next_page = function() { - if (loading) return; - loading = true; - - var this_page = $(".pages a.selected:last"); - var next_page = this_page.next(); - - var href = next_page.prop("href"); - if (!href) return; - - var boardheader = $('

'+_('Page')+' '+next_page.html()+'

'); - var loading_ind = $('

'+_('Loading...')+'

').insertBefore('form[name="postcontrols"]>.delete:first'); - - $.get(href, function(data) { - var doc = $(data); - - loading_ind.remove(); - boardheader.insertBefore('form[name="postcontrols"]>.delete:first'); - - var i = 0; - - doc.find('div[id*="thread_"]').each(function() { - var checkout = $(this).attr('id').replace('thread_', ''); - var $this = this; - - if ($('div#thread_' + checkout).length == 0) { - // Delay DOM insertion to lessen the lag. - setTimeout(function() { - $($this).insertBefore('form[name="postcontrols"]>.delete:first'); - $(document).trigger('new_post', $this); - $($this).hide().slideDown(); - }, 500*i); - i++; - } - }); - setTimeout(function() { - loading = false; - scrolltest(); - }, 500*(i+1)); - - next_page.addClass('selected'); - }); - + if (loading) return; + loading = true; + + var this_page = $(".pages a.selected:last"); + var next_page = this_page.next(); + + var href = next_page.prop("href"); + if (!href) return; + + var boardheader = $('

'+_('Page')+' '+next_page.html()+'

'); + var loading_ind = $('

'+_('Loading...')+'

').insertBefore('#post-moderation-fields'); + + $.get(href, function(data) { + var doc = $(data); + + loading_ind.remove(); + boardheader.insertBefore('#post-moderation-fields'); + + var i = 0; + + doc.find('div[id*="thread_"]').each(function() { + var checkout = $(this).attr('id').replace('thread_', ''); + var $this = this; + + if ($('div#thread_' + checkout).length == 0) { + // Delay DOM insertion to lessen the lag. + setTimeout(function() { + $($this).insertBefore('#post-moderation-fields'); + $(document).trigger('new_post', $this); + $($this).hide().slideDown(); + }, 500*i); + + i++; + } + }); + + setTimeout(function() { + loading = false; + scrolltest(); + }, 500*(i+1)); + + next_page.addClass('selected'); + }); }; var button = $(""+_("All")+" ").prependTo(".pages"); diff --git a/js/inline-expanding.js b/js/inline-expanding.js index 4d0cb03d..5db95c09 100644 --- a/js/inline-expanding.js +++ b/js/inline-expanding.js @@ -7,65 +7,201 @@ * Copyright (c) 2013-2014 Marcin Łabanowski * * Usage: - * // $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/jquery.min.js'; * $config['additional_javascript'][] = 'js/inline-expanding.js'; * */ -onready(function(){ +$(document).ready(function(){ + 'use strict'; + + var DEFAULT_MAX = 5; // default maximum image loads var inline_expand_post = function() { var link = this.getElementsByTagName('a'); - for (var i = 0; i < link.length; i++) { - if (typeof link[i] == "object" && link[i].childNodes && typeof link[i].childNodes[0] !== 'undefined' && link[i].childNodes[0].src && link[i].childNodes[0].className.match(/post-image/) && !link[i].className.match(/file/)) { - link[i].childNodes[0].style.maxWidth = '98%'; - link[i].onclick = function(e) { - if (this.childNodes[0].className == 'hidden') - return false; - if (e.which == 2 || e.metaKey) - return true; - if (!this.dataset.src) { - this.parentNode.removeAttribute('style'); - this.dataset.expanded = 'true'; + var loadingQueue = (function () { + var MAX_IMAGES = localStorage.inline_expand_max || DEFAULT_MAX; // maximum number of images to load concurrently, 0 to disable + var loading = 0; // number of images that is currently loading + var waiting = []; // waiting queue - if (this.childNodes[0].tagName === 'CANVAS') { - this.removeChild(this.childNodes[0]); - this.childNodes[0].style.display = 'block'; - } - - this.dataset.src= this.childNodes[0].src; - this.dataset.width = this.childNodes[0].style.width; - this.dataset.height = this.childNodes[0].style.height; - - - this.childNodes[0].src = this.href; - this.childNodes[0].style.width = 'auto'; - this.childNodes[0].style.height = 'auto'; - this.childNodes[0].style.opacity = '0.4'; - this.childNodes[0].style.filter = 'alpha(opacity=40)'; - this.childNodes[0].onload = function() { - this.style.opacity = ''; - delete this.style.filter; - } + var enqueue = function (ele) { + waiting.push(ele); + }; + var dequeue = function () { + return waiting.shift(); + }; + var update = function() { + var ele; + while (loading < MAX_IMAGES || MAX_IMAGES === 0) { + ele = dequeue(); + if (ele) { + ++loading; + ele.deferred.resolve(); } else { + return; + } + } + }; + return { + remove: function (ele) { + var i = waiting.indexOf(ele); + if (i > -1) { + waiting.splice(i, 1); + } + if ($(ele).data('imageLoading') === 'true') { + $(ele).data('imageLoading', 'false'); + clearTimeout(ele.timeout); + --loading; + } + }, + add: function (ele) { + ele.deferred = $.Deferred(); + ele.deferred.done(function () { + var $loadstart = $.Deferred(); + var thumb = ele.childNodes[0]; + var img = ele.childNodes[1]; + + var onLoadStart = function (img) { + if (img.naturalWidth) { + $loadstart.resolve(img, thumb); + } else { + return (ele.timeout = setTimeout(onLoadStart, 30, img)); + } + }; + + $(img).one('load', function () { + $.when($loadstart).done(function () { + // once fully loaded, update the waiting queue + --loading; + $(ele).data('imageLoading', 'false'); + update(); + }); + }); + $loadstart.done(function (img, thumb) { + thumb.style.display = 'none'; + img.style.display = ''; + }); + + img.setAttribute('src', ele.href); + $(ele).data('imageLoading', 'true'); + ele.timeout = onLoadStart(img); + }); + + if (loading < MAX_IMAGES || MAX_IMAGES === 0) { + ++loading; + ele.deferred.resolve(); + } else { + enqueue(ele); + } + + } + }; + })(); + + for (var i = 0; i < link.length; i++) { + if (typeof link[i] == "object" && link[i].childNodes && typeof link[i].childNodes[0] !== 'undefined' && + link[i].childNodes[0].src && link[i].childNodes[0].className.match(/post-image/) && !link[i].className.match(/file/)) { + link[i].onclick = function(e) { + var img, post_body, still_open, canvas, scroll; + var thumb = this.childNodes[0]; + var padding = 5; + var boardlist = $('.boardlist')[0]; + + + if (thumb.className == 'hidden') + return false; + if (e.which == 2 || e.ctrlKey) // open in new tab + return true; + if (!$(this).data('expanded')) { + if (~this.parentNode.className.indexOf('multifile')) - this.parentNode.style.width = (parseInt(this.dataset.width)+40)+'px'; - this.childNodes[0].src = this.dataset.src; - this.childNodes[0].style.width = this.dataset.width; - this.childNodes[0].style.height = this.dataset.height; - delete this.dataset.expanded; - delete this.dataset.src; - delete this.childNodes[0].style.opacity; - delete this.childNodes[0].style.filter; + $(this).data('width', this.parentNode.style.width); + + this.parentNode.removeAttribute('style'); + $(this).data('expanded', 'true'); + + if (thumb.tagName === 'CANVAS') { + canvas = thumb; + thumb = thumb.nextSibling; + this.removeChild(canvas); + canvas.style.display = 'block'; + } + + thumb.style.opacity = '0.4'; + thumb.style.filter = 'alpha(opacity=40)'; + + img = document.createElement('img'); + img.className = 'full-image'; + img.style.display = 'none'; + img.setAttribute('alt', 'Fullsized image'); + this.appendChild(img); + + loadingQueue.add(this); + } else { + loadingQueue.remove(this); + + scroll = false; + + // scroll to thumb if not triggered by 'shrink all image' + if (e.target.className == 'full-image') { + scroll = true; + } + + if (~this.parentNode.className.indexOf('multifile')) + this.parentNode.style.width = $(this).data('width'); + + thumb.style.opacity = ''; + thumb.style.display = ''; + if (thumb.nextSibling) this.removeChild(thumb.nextSibling); //full image loaded or loading + $(this).removeData('expanded'); + delete thumb.style.filter; + + // do the scrolling after page reflow + if (scroll) { + post_body = $(thumb).parentsUntil('form > div').last(); + + // on multifile posts, determin how many other images are still expanded + still_open = post_body.find('.post-image').filter(function(){ + return $(this).parent().data('expanded') == 'true'; + }).length; + + // deal with differnt boards' menu styles + if ($(boardlist).css('position') == 'fixed') + padding += boardlist.getBoundingClientRect().height; + + if (still_open > 0) { + if (thumb.getBoundingClientRect().top - padding < 0) + $(document).scrollTop($(thumb).parent().parent().offset().top - padding); + } else { + if (post_body[0].getBoundingClientRect().top - padding < 0) + $(document).scrollTop(post_body.offset().top - padding); + } + } if (localStorage.no_animated_gif === 'true' && typeof unanimate_gif === 'function') { - unanimate_gif(this.childNodes[0]); + unanimate_gif(thumb); } } return false; - } + }; } } + }; + + // setting up user option + if (window.Options && Options.get_tab('general')) { + Options.extend_tab('general', ''+ _('Number of simultaneous image downloads (0 to disable): ') + + ''); + $('#inline-expand-max input') + .css('width', '50px') + .val(localStorage.inline_expand_max || DEFAULT_MAX) + .on('change', function (e) { + // validation in case some fucktard tries to enter a negative floating point number + var n = parseInt(e.target.value); + var val = (n<0) ? 0 : n; + + localStorage.inline_expand_max = val; + }); } if (window.jQuery) { diff --git a/js/inline.js b/js/inline.js index 4f8c9a72..e581b79d 100644 --- a/js/inline.js +++ b/js/inline.js @@ -123,8 +123,8 @@ $(document).ready(function() { $clone.insertAfter(link.node) } - App.options.add('useInlining', 'Enable inlining') - App.options.add('hidePost', 'Hide inlined backlinked posts') + App.options.add('useInlining', _('Enable inlining')) + App.options.add('hidePost', _('Hide inlined backlinked posts')) $('head').append( '').appendTo($('head')); var hideImage = function() { - if ($(this).parent()[0].dataset.expanded == 'true') { + if ($(this).parent().data('expanded') == 'true') { $(this).parent().click(); } $(this) diff --git a/log.php b/log.php new file mode 100644 index 00000000..1a660c4c --- /dev/null +++ b/log.php @@ -0,0 +1,24 @@ + 'log', // modlog '/log/(\d+)' => 'log', // modlog - '/log:([^/]+)' => 'user_log', // modlog - '/log:([^/]+)/(\d+)' => 'user_log', // modlog - '/news' => 'secure_POST news', // view news - '/news/(\d+)' => 'secure_POST news', // view news - '/news/delete/(\d+)' => 'secure news_delete', // delete from news + '/log:([^/:]+)' => 'user_log', // modlog + '/log:([^/:]+)/(\d+)' => 'user_log', // modlog + '/log:b:([^/]+)' => 'board_log', // modlog + '/log:b:([^/]+)/(\d+)' => 'board_log', // modlog + + '/edit_news' => 'secure_POST news', // view news + '/edit_news/(\d+)' => 'secure_POST news', // view news + '/edit_news/delete/(\d+)' => 'secure news_delete', // delete from news + + '/edit_pages(?:/?(\%b)?)' => 'secure_POST pages', + '/edit_page/(\d+)' => 'secure_POST edit_page', + '/edit_pages/delete/([a-z0-9]+)' => 'secure delete_page', + '/edit_pages/delete/([a-z0-9]+)/(\%b)' => 'secure delete_page_board', '/noticeboard' => 'secure_POST noticeboard', // view noticeboard '/noticeboard/(\d+)' => 'secure_POST noticeboard', // view noticeboard @@ -82,6 +82,7 @@ $pages = array( '/(\%b)/deletebyip/(\d+)(/global)?' => 'secure deletebyip', // delete all posts by IP address '/(\%b)/(un)?lock/(\d+)' => 'secure lock', // lock thread '/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread + '/(\%b)/(un)?cycle/(\d+)' => 'secure cycle', // cycle thread '/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread '/themes' => 'themes_list', // manage themes @@ -93,10 +94,10 @@ $pages = array( '/config/(\%b)' => 'secure_POST config', // config editor // these pages aren't listed in the dashboard without $config['debug'] - '/debug/antispam' => 'debug_antispam', - '/debug/recent' => 'debug_recent_posts', - '/debug/apc' => 'debug_apc', - '/debug/sql' => 'secure_POST debug_sql', + //'/debug/antispam' => 'debug_antispam', + //'/debug/recent' => 'debug_recent_posts', + //'/debug/apc' => 'debug_apc', + //'/debug/sql' => 'secure_POST debug_sql', // This should always be at the end: '/(\%b)/' => 'view_board', diff --git a/post.php b/post.php index 729170a7..bef4b3c2 100644 --- a/post.php +++ b/post.php @@ -7,20 +7,174 @@ require_once 'inc/functions.php'; require_once 'inc/anti-bot.php'; require_once 'inc/bans.php'; -// Fix for magic quotes -if (get_magic_quotes_gpc()) { - function strip_array($var) { - return is_array($var) ? array_map('strip_array', $var) : stripslashes($var); - } - - $_GET = strip_array($_GET); - $_POST = strip_array($_POST); -} - if ((!isset($_POST['mod']) || !$_POST['mod']) && $config['board_locked']) { error("Board is locked"); } +$dropped_post = false; + +// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post. +if (isset($_GET['Newsgroups']) && $config['nntpchan']['enabled']) { + if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) { + error("NNTPChan: Forbidden. $_SERVER[REMOTE_ADDR] is not a trusted peer"); + } + + $_POST = array(); + $_POST['json_response'] = true; + + $headers = json_encode($_GET); + + if (!isset ($_GET['Message-Id'])) { + if (!isset ($_GET['Message-ID'])) { + error("NNTPChan: No message ID"); + } + else $msgid = $_GET['Message-ID']; + } + else $msgid = $_GET['Message-Id']; + + $groups = preg_split("/,\s*/", $_GET['Newsgroups']); + if (count($groups) != 1) { + error("NNTPChan: Messages can go to only one newsgroup"); + } + $group = $groups[0]; + + if (!isset($config['nntpchan']['dispatch'][$group])) { + error("NNTPChan: We don't synchronize $group"); + } + $xboard = $config['nntpchan']['dispatch'][$group]; + + $ref = null; + if (isset ($_GET['References'])) { + $refs = preg_split("/,\s*/", $_GET['References']); + + if (count($refs) > 1) { + error("NNTPChan: We don't support multiple references"); + } + + $ref = $refs[0]; + + $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id` = :ref"); + $query->bindValue(':ref', $ref); + $query->execute() or error(db_error($query)); + + $ary = $query->fetchAll(PDO::FETCH_ASSOC); + + if (count($ary) == 0) { + error("NNTPChan: We don't have $ref that $msgid references"); + } + + $p_id = $ary[0]['id']; + $p_board = $ary[0]['board']; + + if ($p_board != $xboard) { + error("NNTPChan: Cross board references not allowed. Tried to reference $p_board on $xboard"); + } + + $_POST['thread'] = $p_id; + } + + $date = isset($_GET['Date']) ? strtotime($_GET['Date']) : time(); + + list($ct) = explode('; ', $_GET['Content-Type']); + + $query = prepare("SELECT COUNT(*) AS `c` FROM ``nntp_references`` WHERE `message_id` = :msgid"); + $query->bindValue(":msgid", $msgid); + $query->execute() or error(db_error($query)); + + $a = $query->fetch(PDO::FETCH_ASSOC); + if ($a['c'] > 0) { + error("NNTPChan: We already have this post. Post discarded."); + } + + if ($ct == 'text/plain') { + $content = file_get_contents("php://input"); + } + elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') { + _syslog(LOG_INFO, "MM: Files: ".print_r($GLOBALS, true)); // Debug + + $content = ''; + + $newfiles = array(); + foreach ($_FILES['attachment']['error'] as $id => $error) { + if ($_FILES['attachment']['type'][$id] == 'text/plain') { + $content .= file_get_contents($_FILES['attachment']['tmp_name'][$id]); + } + elseif ($_FILES['attachment']['type'][$id] == 'message/rfc822') { // Signed message, ignore for now + } + else { // A real attachment :^) + $file = array(); + $file['name'] = $_FILES['attachment']['name'][$id]; + $file['type'] = $_FILES['attachment']['type'][$id]; + $file['size'] = $_FILES['attachment']['size'][$id]; + $file['tmp_name'] = $_FILES['attachment']['tmp_name'][$id]; + $file['error'] = $_FILES['attachment']['error'][$id]; + + $newfiles["file$id"] = $file; + } + } + + $_FILES = $newfiles; + } + else { + error("NNTPChan: Wrong mime type: $ct"); + } + + $_POST['subject'] = isset($_GET['Subject']) ? ($_GET['Subject'] == 'None' ? '' : $_GET['Subject']) : ''; + $_POST['board'] = $xboard; + + if (isset ($_GET['From'])) { + list($name, $mail) = explode(" <", $_GET['From'], 2); + $mail = preg_replace('/>\s+$/', '', $mail); + + $_POST['name'] = $name; + //$_POST['email'] = $mail; + $_POST['email'] = ''; + } + + if (isset ($_GET['X_Sage'])) { + $_POST['email'] = 'sage'; + } + + $content = preg_replace_callback('/>>([0-9a-fA-F]{6,})/', function($id) use ($xboard) { + $id = $id[1]; + + $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id_digest` LIKE :rule"); + $idx = $id . "%"; + $query->bindValue(':rule', $idx); + $query->execute() or error(db_error($query)); + + $ary = $query->fetchAll(PDO::FETCH_ASSOC); + if (count($ary) == 0) { + return ">>>>$id"; + } + else { + $ret = array(); + foreach ($ary as $v) { + if ($v['board'] != $xboard) { + $ret[] = ">>>/".$v['board']."/".$v['id']; + } + else { + $ret[] = ">>".$v['id']; + } + } + return implode($ret, ", "); + } + }, $content); + + $_POST['body'] = $content; + + $dropped_post = array( + 'date' => $date, + 'board' => $xboard, + 'msgid' => $msgid, + 'headers' => $headers, + 'from_nntp' => true, + ); +} +elseif (isset($_GET['Newsgroups'])) { + error("NNTPChan: NNTPChan support is disabled"); +} + if (isset($_POST['delete'])) { // Delete @@ -56,7 +210,7 @@ if (isset($_POST['delete'])) { error($config['error']['nodelete']); foreach ($delete as &$id) { - $query = prepare(sprintf("SELECT `thread`, `time`,`password` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); + $query = prepare(sprintf("SELECT `id`,`thread`,`time`,`password` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); @@ -80,11 +234,13 @@ if (isset($_POST['delete'])) { if (isset($_POST['file'])) { // Delete just the file deleteFile($id); + modLog("User deleted file from his own post #$id"); } else { // Delete entire post deletePost($id); + modLog("User deleted his own post #$id"); } - + _syslog(LOG_INFO, 'Deleted post: ' . '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') ); @@ -131,23 +287,49 @@ if (isset($_POST['delete'])) { if (empty($report)) error($config['error']['noreport']); + + if (strlen($report) > 30) + error($config['error']['invalidreport']); if (count($report) > $config['report_limit']) error($config['error']['toomanyreports']); + + if ($config['report_captcha'] && !isset($_POST['captcha_text'], $_POST['captcha_cookie'])) { + error($config['error']['bot']); + } + + if ($config['report_captcha']) { + $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']); + } + } $reason = escape_markup_modifiers($_POST['reason']); markup($reason); - + foreach ($report as &$id) { - $query = prepare(sprintf("SELECT `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); + $query = prepare(sprintf("SELECT `id`, `thread` FROM ``posts_%s`` WHERE `id` = :id", $board['uri'])); $query->bindValue(':id', $id, PDO::PARAM_INT); $query->execute() or error(db_error($query)); - $thread = $query->fetchColumn(); + $post = $query->fetch(PDO::FETCH_ASSOC); + $error = event('report', array('ip' => $_SERVER['REMOTE_ADDR'], 'board' => $board['uri'], 'post' => $post, 'reason' => $reason, 'link' => link_for($post))); + + if ($error) { + error($error); + } + if ($config['syslog']) _syslog(LOG_INFO, 'Reported post: ' . - '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($thread ? '#' . $id : '') . + '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') . ' for "' . $reason . '"' ); $query = prepare("INSERT INTO ``reports`` VALUES (NULL, :time, :ip, :board, :post, :reason)"); @@ -163,13 +345,14 @@ if (isset($_POST['delete'])) { $root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root']; if (!isset($_POST['json_response'])) { - header('Location: ' . $root . $board['dir'] . $config['file_index'], true, $config['redirect_http']); + $index = $root . $board['dir'] . $config['file_index']; + echo Element('page.html', array('config' => $config, 'body' => '', 'title' => _('Report submitted!'))); } else { header('Content-Type: text/json'); echo json_encode(array('success' => true)); } -} elseif (isset($_POST['post'])) { - if (!isset($_POST['body'], $_POST['board'])) +} elseif (isset($_POST['post']) || $dropped_post) { + if (!isset($_POST['body'], $_POST['board']) && !$dropped_post) error($config['error']['bot']); $post = array('board' => $_POST['board'], 'files' => array()); @@ -196,66 +379,87 @@ if (isset($_POST['delete'])) { } else $post['op'] = true; - // Check for CAPTCHA right after opening the board so the "return" link is in there - if ($config['recaptcha']) { - if (!isset($_POST['recaptcha_challenge_field']) || !isset($_POST['recaptcha_response_field'])) + + if (!$dropped_post) { + // Check for CAPTCHA right after opening the board so the "return" link is in there + if ($config['recaptcha']) { + if (!isset($_POST['g-recaptcha-response'])) + error($config['error']['bot']); + + // Check what reCAPTCHA has to say... + $resp = json_decode(file_get_contents(sprintf('https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s', + $config['recaptcha_private'], + urlencode($_POST['g-recaptcha-response']), + $_SERVER['REMOTE_ADDR'])), true); + + if (!$resp['success']) { + error($config['error']['captcha']); + } + // Same, but now with our custom captcha provider + if (($config['captcha']['enabled']) || (($post['op']) && ($config['new_thread_capt'])) ) { + $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']); - // Check what reCAPTCHA has to say... - $resp = recaptcha_check_answer($config['recaptcha_private'], - $_SERVER['REMOTE_ADDR'], - $_POST['recaptcha_challenge_field'], - $_POST['recaptcha_response_field']); - if (!$resp->is_valid) { - error($config['error']['captcha']); - } - } - - if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) || - (!$post['op'] && $_POST['post'] == $config['button_reply']))) - error($config['error']['bot']); - // Check the referrer - if ($config['referer_match'] !== false && - (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER'])))) - error($config['error']['referer']); + // Check the referrer + if ($config['referer_match'] !== false && + (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER'])))) + error($config['error']['referer']); - checkDNSBL(); + checkDNSBL(); - // Check if banned - checkBan($board['uri']); + // Check if banned + checkBan($board['uri']); - if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { - require 'inc/mod/auth.php'; - if (!$mod) { - // Liar. You're not a mod. - error($config['error']['notamod']); + if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { + check_login(false); + if (!$mod) { + // Liar. You're not a mod. + error($config['error']['notamod']); + } + + $post['sticky'] = $post['op'] && isset($_POST['sticky']); + $post['locked'] = $post['op'] && isset($_POST['lock']); + $post['raw'] = isset($_POST['raw']); + + if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) + error($config['error']['noaccess']); + if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) + error($config['error']['noaccess']); + if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) + error($config['error']['noaccess']); } - $post['sticky'] = $post['op'] && isset($_POST['sticky']); - $post['locked'] = $post['op'] && isset($_POST['lock']); - $post['raw'] = isset($_POST['raw']); - - if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) - error($config['error']['noaccess']); - if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) - error($config['error']['noaccess']); - if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) - error($config['error']['noaccess']); - } + if (!$post['mod']) { + $post['antispam_hash'] = checkSpam(array($board['uri'], isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int)$_POST['page'] : null))); + if ($post['antispam_hash'] === true) + error($config['error']['spam']); + } - if (!$post['mod']) { - $post['antispam_hash'] = checkSpam(array($board['uri'], isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int)$_POST['page'] : null))); - if ($post['antispam_hash'] === true) - error($config['error']['spam']); + if ($config['robot_enable'] && $config['robot_mute']) { + checkMute(); + } } - - if ($config['robot_enable'] && $config['robot_mute']) { - checkMute(); + else { + $mod = $post['mod'] = false; } //Check if thread exists if (!$post['op']) { - $query = prepare(sprintf("SELECT `sticky`,`locked`,`sage`,`slug` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri'])); + $query = prepare(sprintf("SELECT `sticky`,`locked`,`cycle`,`sage`,`slug` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri'])); $query->bindValue(':id', $post['thread'], PDO::PARAM_INT); $query->execute() or error(db_error()); @@ -361,28 +565,36 @@ if (isset($_POST['delete'])) { $post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email'])); $post['body'] = $_POST['body']; $post['password'] = $_POST['password']; - $post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || !empty($_FILES['file']['name']))); + $post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || count($_FILES) > 0)); - if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { - $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); - if ($stripped_whitespace == '') { - error($config['error']['tooshort_body']); + if (!$dropped_post) { + + if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { + $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); + if ($stripped_whitespace == '') { + error($config['error']['tooshort_body']); + } + } + + if (!$post['op']) { + // Check if thread is locked + // but allow mods to post + if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) + error($config['error']['locked']); + + $numposts = numPosts($post['thread']); + + if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $numposts['replies']) + error($config['error']['reply_hard_limit']); + + if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $numposts['images']) + error($config['error']['image_hard_limit']); } } - - if (!$post['op']) { - // Check if thread is locked - // but allow mods to post - if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) - error($config['error']['locked']); - - $numposts = numPosts($post['thread']); - - if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $numposts['replies']) - error($config['error']['reply_hard_limit']); - - if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $numposts['images']) - error($config['error']['image_hard_limit']); + else { + if (!$post['op']) { + $numposts = numPosts($post['thread']); + } } if ($post['has_file']) { @@ -432,7 +644,7 @@ if (isset($_POST['delete'])) { $trip = generate_tripcode($post['name']); $post['name'] = $trip[0]; - $post['trip'] = isset($trip[1]) ? $trip[1] : ''; + $post['trip'] = isset($trip[1]) ? $trip[1] : ''; // XX: Dropped posts and tripcodes $noko = false; if (strtolower($post['email']) == 'noko') { @@ -447,7 +659,7 @@ if (isset($_POST['delete'])) { $i = 0; foreach ($_FILES as $key => $file) { if ($file['size'] && $file['tmp_name']) { - $file['filename'] = urldecode(get_magic_quotes_gpc() ? stripslashes($file['name']) : $file['name']); + $file['filename'] = urldecode($file['name']); $file['extension'] = strtolower(mb_substr($file['filename'], mb_strrpos($file['filename'], '.') + 1)); if (isset($config['filename_func'])) $file['file_id'] = $config['filename_func']($file); @@ -467,15 +679,17 @@ if (isset($_POST['delete'])) { if (empty($post['files'])) $post['has_file'] = false; - // Check for a file - if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { - if (!$post['has_file'] && $config['force_image_op']) - error($config['error']['noimage']); - } + if (!$dropped_post) { + // Check for a file + if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { + if (!$post['has_file'] && $config['force_image_op']) + error($config['error']['noimage']); + } - // Check for too many files - if (sizeof($post['files']) > $config['max_images']) - error($config['error']['toomanyimages']); + // Check for too many files + if (sizeof($post['files']) > $config['max_images']) + error($config['error']['toomanyimages']); + } if ($config['strip_combining_chars']) { $post['name'] = strip_combining_chars($post['name']); @@ -484,18 +698,19 @@ if (isset($_POST['delete'])) { $post['body'] = strip_combining_chars($post['body']); } - // Check string lengths - if (mb_strlen($post['name']) > 35) - error(sprintf($config['error']['toolong'], 'name')); - if (mb_strlen($post['email']) > 40) - error(sprintf($config['error']['toolong'], 'email')); - if (mb_strlen($post['subject']) > 100) - error(sprintf($config['error']['toolong'], 'subject')); - if (!$mod && mb_strlen($post['body']) > $config['max_body']) - error($config['error']['toolong_body']); - if (mb_strlen($post['password']) > 20) - error(sprintf($config['error']['toolong'], 'password')); - + if (!$dropped_post) { + // Check string lengths + if (mb_strlen($post['name']) > 35) + error(sprintf($config['error']['toolong'], 'name')); + if (mb_strlen($post['email']) > 40) + error(sprintf($config['error']['toolong'], 'email')); + if (mb_strlen($post['subject']) > 100) + error(sprintf($config['error']['toolong'], 'subject')); + if (!$mod && mb_strlen($post['body']) > $config['max_body']) + error($config['error']['toolong_body']); + if (mb_strlen($post['password']) > 20) + error(sprintf($config['error']['toolong'], 'password')); + } wordfilters($post['body']); $post['body'] = escape_markup_modifiers($post['body']); @@ -504,6 +719,7 @@ if (isset($_POST['delete'])) { $post['body'] .= "\n1"; } + if (!$dropped_post) if (($config['country_flags'] && !$config['allow_no_country']) || ($config['country_flags'] && $config['allow_no_country'] && !isset($_POST['no_country']))) { require 'inc/lib/geoip/geoip.inc'; $gi=geoip\geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD); @@ -545,6 +761,7 @@ if (isset($_POST['delete'])) { $post['body'] .= "\n" . $_POST['tag'] . ""; } + if (!$dropped_post) if ($config['proxy_save'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $proxy = preg_replace("/[^0-9a-fA-F.,: ]/", '', $_SERVER['HTTP_X_FORWARDED_FOR']); $post['body'] .= "\n".$proxy.""; @@ -572,7 +789,12 @@ if (isset($_POST['delete'])) { if ($post['has_file']) { - $fnarray = array(); + $md5cmd = false; + if ($config['bsd_md5']) $md5cmd = '/sbin/md5 -r'; + if ($config['gnu_md5']) $md5cmd = 'md5sum'; + + $allhashes = ''; + foreach ($post['files'] as $key => &$file) { if ($post['op'] && $config['allowed_ext_op']) { if (!in_array($file['extension'], $config['allowed_ext_op'])) @@ -586,51 +808,48 @@ if (isset($_POST['delete'])) { // Truncate filename if it is too long $file['filename'] = mb_substr($file['filename'], 0, $config['max_filename_len']); - if (!isset($filenames)) { - $filenames = escapeshellarg($file['tmp_name']); - } else { - $filenames .= (' ' . escapeshellarg($file['tmp_name'])); - } - - $fnarray[] = $file['tmp_name']; - $upload = $file['tmp_name']; if (!is_readable($upload)) error($config['error']['nomove']); - } - - $md5cmd = $config['bsd_md5'] ? 'md5 -r' : 'md5sum'; - if (!$config['php_md5'] && $output = shell_exec_error("cat $filenames | $md5cmd")) { - $explodedvar = explode(' ', $output); - $hash = $explodedvar[0]; - $post['filehash'] = $hash; - } elseif ($config['max_images'] === 1) { - $post['filehash'] = md5_file($upload); - } else { - $str_to_hash = ''; - foreach ($fnarray as $i => $f) { - $str_to_hash .= file_get_contents($f); + if ($md5cmd) { + $output = shell_exec_error($md5cmd . " " . escapeshellarg($upload)); + $output = explode(' ', $output); + $hash = $output[0]; } - $post['filehash'] = md5($str_to_hash); + else { + $hash = md5_file($upload); + } + + $file['hash'] = $hash; + $allhashes .= $hash; + } + + if (count ($post['files']) == 1) { + $post['filehash'] = $hash; + } + else { + $post['filehash'] = md5($allhashes); } } - - if (!hasPermission($config['mod']['bypass_filters'], $board['uri'])) { - require_once 'inc/filters.php'; - + + if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { + require_once 'inc/filters.php'; + do_filters($post); } - - if ($post['has_file']) { + + if ($post['has_file']) { foreach ($post['files'] as $key => &$file) { - if ($file['is_an_image'] && $config['ie_mime_type_detection'] !== false) { - // Check IE MIME type detection XSS exploit - $buffer = file_get_contents($upload, null, null, null, 255); - if (preg_match($config['ie_mime_type_detection'], $buffer)) { - undoImage($post); - error($config['error']['mime_exploit']); + if ($file['is_an_image']) { + if ($config['ie_mime_type_detection'] !== false) { + // Check IE MIME type detection XSS exploit + $buffer = file_get_contents($upload, null, null, null, 255); + if (preg_match($config['ie_mime_type_detection'], $buffer)) { + undoImage($post); + error($config['error']['mime_exploit']); + } } require_once 'inc/image.php'; @@ -639,6 +858,9 @@ if (isset($_POST['delete'])) { if (!$size = @getimagesize($file['tmp_name'])) { error($config['error']['invalidimg']); } + if (!in_array($size[2], array(IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_BMP))) { + error($config['error']['invalidimg']); + } if ($size[0] > $config['max_width'] || $size[1] > $config['max_height']) { error($config['error']['maxsize']); } @@ -746,6 +968,34 @@ if (isset($_POST['delete'])) { $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } + + if ($config['tesseract_ocr'] && $file['thumb'] != 'file') { // Let's OCR it! + $fname = $file['tmp_name']; + + if ($file['height'] > 500 || $file['width'] > 500) { + $fname = $file['thumb']; + } + + if ($fname == 'spoiler') { // We don't have that much CPU time, do we? + } + else { + $tmpname = "tmp/tesseract/".rand(0,10000000); + + // Preprocess command is an ImageMagick b/w quantization + $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " . + 'tesseract stdin '.escapeshellarg($tmpname).' '.$config['tesseract_params']); + $tmpname .= ".txt"; + + $value = @file_get_contents($tmpname); + @unlink($tmpname); + + if ($value && trim($value)) { + // This one has an effect, that the body is appended to a post body. So you can write a correct + // spamfilter. + $post['body_nomarkup'] .= "".htmlspecialchars($value).""; + } + } + } if (!isset($dont_copy_file) || !$dont_copy_file) { if (isset($file['file_tmp'])) { @@ -786,7 +1036,12 @@ if (isset($_POST['delete'])) { } } - if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { + // Do filters again if OCRing + if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { + do_filters($post); + } + + if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) { undoImage($post); if ($config['robot_mute']) { error(sprintf($config['error']['muted'], mute())); @@ -825,7 +1080,52 @@ if (isset($_POST['delete'])) { $post['id'] = $id = post($post); $post['slug'] = slugify($post); + + if ($dropped_post && $dropped_post['from_nntp']) { + $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES ". + "(:board , :id , :message_id , :message_id_digest , false, :headers)"); + + $query->bindValue(':board', $dropped_post['board']); + $query->bindValue(':id', $id); + $query->bindValue(':message_id', $dropped_post['msgid']); + $query->bindValue(':message_id_digest', sha1($dropped_post['msgid'])); + $query->bindValue(':headers', $dropped_post['headers']); + $query->execute() or error(db_error($query)); + } // ^^^^^ For inbound posts ^^^^^ + elseif ($config['nntpchan']['enabled'] && $config['nntpchan']['group']) { + // vvvvv For outbound posts vvvvv + + require_once('inc/nntpchan/nntpchan.php'); + $msgid = gen_msgid($post['board'], $post['id']); + + list($headers, $files) = post2nntp($post, $msgid); + + $message = gen_nntp($headers, $files); + + $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES ". + "(:board , :id , :message_id , :message_id_digest , true , :headers)"); + + $query->bindValue(':board', $post['board']); + $query->bindValue(':id', $post['id']); + $query->bindValue(':message_id', $msgid); + $query->bindValue(':message_id_digest', sha1($msgid)); + $query->bindValue(':headers', json_encode($headers)); + $query->execute() or error(db_error($query)); + + // Let's broadcast it! + nntp_publish($message, $msgid); + } + insertFloodPost($post); + + // Handle cyclical threads + if (!$post['op'] && isset($thread['cycle']) && $thread['cycle']) { + // Query is a bit weird due to "This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" (MariaDB Ver 15.1 Distrib 10.0.17-MariaDB, for Linux (x86_64)) + $query = prepare(sprintf('DELETE FROM ``posts_%s`` WHERE `thread` = :thread AND `id` NOT IN (SELECT `id` FROM (SELECT `id` FROM ``posts_%s`` WHERE `thread` = :thread ORDER BY `id` DESC LIMIT :limit) i)', $board['uri'], $board['uri'])); + $query->bindValue(':thread', $post['thread']); + $query->bindValue(':limit', $config['cycle_limit'], PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + } if (isset($post['antispam_hash'])) { incrementSpamHash($post['antispam_hash']); @@ -905,7 +1205,7 @@ if (isset($_POST['delete'])) { $build_pages = range(1, $config['max_pages']); if ($post['op']) - clean(); + clean($id); event('post-after', $post); diff --git a/report.php b/report.php new file mode 100644 index 00000000..e09b1075 --- /dev/null +++ b/report.php @@ -0,0 +1,19 @@ + $global, 'post' => $post, 'board' => $board, 'captcha' => $captcha, 'config' => $config]); +echo Element('page.html', ['config' => $config, 'body' => $body]); diff --git a/search.php b/search.php index 71f4ae5e..394aa3ff 100644 --- a/search.php +++ b/search.php @@ -15,7 +15,7 @@ $boards = listBoards(TRUE); } - $body = Element('search_form.html', Array('boards' => $boards, 'b' => isset($_GET['board']) ? $_GET['board'] : false, 'search' => isset($_GET['search']) ? str_replace('"', '"', utf8tohtml($_GET['search'])) : false)); + $body = Element('search_form.html', Array('boards' => $boards, 'board' => isset($_GET['board']) ? $_GET['board'] : false, 'search' => isset($_GET['search']) ? str_replace('"', '"', utf8tohtml($_GET['search'])) : false)); if(isset($_GET['search']) && !empty($_GET['search']) && isset($_GET['board']) && in_array($_GET['board'], $boards)) { $phrase = $_GET['search']; diff --git a/smart_build.php b/smart_build.php index 31d8110a..cfac446a 100644 --- a/smart_build.php +++ b/smart_build.php @@ -1,163 +1,30 @@ $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"); - return false; - } - - if ($slugcheck && $config['slugify']) { - global $request; - - $link = link_for(array("id" => $thread), $slugcheck === 50, array("uri" => $b)); - $link = "/".$b."/".$config['dir']['res'].$link; - - if ($link != $request) { - header("Location: $link", true, 301); - die(); - } - } - - if ($slugcheck == 50) { // Should we really generate +50 page? Maybe there are not enough posts anyway - global $request; - $r = str_replace("+50", "", $request); - $r = substr($r, 1); // Cut the slash - - if (file_exists($r)) return false; - } - - if (!openBoard($b)) return false; - buildThread($thread); - return true; -} - -function sb_thread_slugcheck($b, $thread) { - return sb_thread($b, $thread, true); -} -function sb_thread_slugcheck50($b, $thread) { - return sb_thread($b, $thread, 50); -} - -function sb_api($b) { global $config, $build_pages; - if (!openBoard($b)) return false; - $config['try_smarter'] = true; - $build_pages = array(-1); - buildIndex(); - return true; -} - -function sb_ukko() { - rebuildTheme("ukko", "post-thread"); - return true; -} - -function sb_catalog($b) { - if (!openBoard($b)) return false; - - rebuildTheme("catalog", "post-thread", $b); - return true; -} - -function sb_recent() { - rebuildTheme("recent", "post-thread"); - return true; -} - -function sb_sitemap() { - rebuildTheme("sitemap", "all"); - return true; -} - -$entrypoints = array(); - -$entrypoints['/%b/'] = 'sb_board'; -$entrypoints['/%b/'.$config['file_index']] = 'sb_board'; -$entrypoints['/%b/'.$config['file_page']] = 'sb_board'; -$entrypoints['/%b/%d.json'] = 'sb_api_board'; -if ($config['api']['enabled']) { - $entrypoints['/%b/threads.json'] = 'sb_api'; - $entrypoints['/%b/catalog.json'] = 'sb_api'; -} - -$entrypoints['/%b/'.$config['dir']['res'].$config['file_page']] = 'sb_thread_slugcheck'; -$entrypoints['/%b/'.$config['dir']['res'].$config['file_page50']] = 'sb_thread_slugcheck50'; -if ($config['slugify']) { - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page_slug']] = 'sb_thread_slugcheck'; - $entrypoints['/%b/'.$config['dir']['res'].$config['file_page50_slug']] = 'sb_thread_slugcheck50'; -} -if ($config['api']['enabled']) { - $entrypoints['/%b/'.$config['dir']['res'].'%d.json'] = 'sb_thread'; -} - -$entrypoints['/*/'] = 'sb_ukko'; -$entrypoints['/*/index.html'] = 'sb_ukko'; -$entrypoints['/recent.html'] = 'sb_recent'; -$entrypoints['/%b/catalog.html'] = 'sb_catalog'; -$entrypoints['/sitemap.xml'] = 'sb_sitemap'; - -$reached = false; - $request = $_SERVER['REQUEST_URI']; -list($request) = explode('?', $request); -foreach ($entrypoints as $id => $fun) { - $id = '@^' . preg_quote($id, '@') . '$@u'; +$route = route($request); - $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 = call_user_func_array($fun, $matches); - - break; - } +if (!$route) { + $reached = false; +} +else { + list ($fun, $args) = $route; + $reached = call_user_func_array($fun, $args); } function die_404() { global $config; @@ -194,6 +61,9 @@ if ($reached) { elseif (preg_match('/\.xml$/', $request)) { header("Content-Type", "application/xml"); } + elseif (preg_match('/\.rss$/', $request)) { + header("Content-Type", "application/rss+xml"); + } else { header("Content-Type", "text/html; charset=utf-8"); } diff --git a/stylesheets/greendark.css b/stylesheets/greendark.css new file mode 100644 index 00000000..ccec5c13 --- /dev/null +++ b/stylesheets/greendark.css @@ -0,0 +1,222 @@ +/* greenddark.css by Z Blanche */ +body { + background:#1b1b1b; + background-image: url(''), url(''); + background-repeat: no-repeat; + background-attachment: fixed, scroll; + background-position: right bottom, 100% 100%; + font-family: arial,helvetica,sans-serif; + font-size: 12pt; + color: #C0C0C0; + cursor: default; + margin: 0 8px; + padding-left: 5px; + padding-right: 5px; +} +div.boardlist { + text-align: center; +} +div.post.reply { + margin-right: px; + margin-bottom: px; + background: #222; + border: 0; + padding: 6px; + border-radius: 6px; + box-shadow: + display: +} +/*media screen*/ +a, a:visited { + text-decoration: underline; + color:#008080 +} +button, input[type=submit], input[type=button] { + cursor: pointer; + border-radius: 5px; + border: 0; + padding: 6px; +} +h1, .subtitle a, .subtitle { + color: #0df211!important; +} +input[type="text"], textarea, input[type=password] { + background: #32d23; + border-radius: 25px; +} +form table tr th { + background: transparent; +} +.post { + padding: 6px; +} +img { + border-radius: 22px; } +hr { + opacity:0.2; +} +label, .subject{ + color:#AAA!important; +} +.name{ + color:#!important; +} +div.pages { + border:0; + background:none; + color: #fff!important; +} +div.pages a.selected { + color: #ff69b4!important; + padding: 4px; + text-decoration: none; +} +div.pages a { + color: #fafafa!important; + padding: 4px; + text-decoration: none; +} +.subtitle a { + display: block; + margin: 7px auto 7px auto; + font-size: 15px; + text-decoration: none; + border: 1px solid #0df211; + padding: 5px; + border-radius: 7px; + max-width: 100px; +} +input[type=text], input[type=password] { + padding: 5px; + font-size: 15px; +} +textarea { + resize:vertical; + max-height: 400px; + width: 250px; + padding: 5px; + font-size: 15px; +} +@-webkit-keyframes Pulse { + from { background-color: #007d9a; + -webkit-box-shadow: 0 0 9px #333; } + 50% { background-color: #2daebf; + -webkit-box-shadow: 0 0 18px #2daebf; } + to { background-color: #007d9a; + -webkit-box-shadow: 0 0 9px #333; } +} +.board_image { + -webkit-animation-name: Pulse; + -webkit-animation-duration: 3s; + -webkit-animation-iteration-count: infinite; + -webkit-transform: rotate(5deg); +} +@-webkit-keyframes shakey { + 0% { -webkit-transform: translate(2px, 1px) rotate(0deg); } + 10% { -webkit-transform: translate(-1px, -2px) rotate(-1deg); } + 20% { -webkit-transform: translate(-3px, 0px) rotate(1deg); } + 30% { -webkit-transform: translate(0px, 2px) rotate(0deg); } + 40% { -webkit-transform: translate(1px, -1px) rotate(1deg); } + 50% { -webkit-transform: translate(-1px, 2px) rotate(-1deg); } + 60% { -webkit-transform: translate(-3px, 1px) rotate(0deg); } + 70% { -webkit-transform: translate(2px, 1px) rotate(-1deg); } + 80% { -webkit-transform: translate(-1px, -1px) rotate(1deg); } + 90% { -webkit-transform: translate(2px, 2px) rotate(0deg); } + 100% { -webkit-transform: translate(1px, -2px) rotate(-1deg); } +} +button:hover, input[type=submit]:hover, input[type=button]:hover, +button:focus, input[type=submit]:focus, input[type=button]:focus +{ + -webkit-animation-name: shakey; + -webkit-animation-duration: 0.1s; + -webkit-transform-origin:50% 50%; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: linear; + background-color: #2daebf; +} +button, input[type=submit], input[type=button] { + -webkit-animation-name: Pulse; + -webkit-animation-duration: 2s; + -webkit-animation-iteration-count: infinite; + color: #ffd; +} +div.ban { + background: #222; + background-image: url(''), url(''); + background-repeat: no-repeat; + background-attachment: fixed, scroll; + background-position: right bottom, 100% 100%; + color:#fff; + border: 0; + border-radius: 6px; + padding: 6px; +} +.desktop-style div.boardlist, .desktop-style div.boardlist:hover { + background:#333!important; + border: 0!important; +} +.theme-catalog div.grid-size-small:hover { + background: #333!important; +} +#options_div { + background: #222!important; +} +select { + background:#333; + color:#eee; + cursor:pointer; + border-radius: 4px; + border: 1px #222 solid; + padding: 4px; +} + ::-webkit-scrollbar { + width: 6px; + height: 8px; + background: #333; + box-shadow: none; +} + ::-webkit-scrollbar-track { + -webkit-box-shadow: none; + background: none; +} + ::-webkit-scrollbar-thumb { + background: #ddd; + width: 6px; + padding: 4px; + border-radius: 10px; + -webkit-box-shadow: 0 0 6px rgba(0,0,0,0.5); +} +header div.subtitle { + font-size: 12pt +} +.desktop-style div.boardlist, .desktop-style div.boardlist:hover { + background: #1b1b1b!important; +} +div.post.reply.highlighted { + background: #FFFFFF; +} +div.banner { + background-color: #1b1b1b; +} + +div.blotter { + color: green; + font-weight: bold; + text-align: center; +} +div.post.reply div.body a { + color: #AAA; +} +p.intro a.email span.name { + color: #cdaf95; +} +div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover { + color: #32DD72; +} +p.intro span.capcode,p.intro a.capcode,p.intro a.nametag { + color: #F00000; + margin-left: 0; +} +table tbody tr:nth-of-type( even ) { + background-color: #1b1b1b; +} diff --git a/stylesheets/img/fade-gray.png b/stylesheets/img/fade-gray.png new file mode 100644 index 00000000..e48aa8eb Binary files /dev/null and b/stylesheets/img/fade-gray.png differ diff --git a/stylesheets/sharp.css b/stylesheets/sharp.css new file mode 100644 index 00000000..7027a51b --- /dev/null +++ b/stylesheets/sharp.css @@ -0,0 +1,401 @@ +/** + * sharp.css + * For AwsumChan by Circlepuller + */ +body { + background: #FFF url('img/fade-gray.png') repeat-x 50% 0%; + color: black; + font-family: arial, helvetica, sans-serif; + font-size: 10pt; + margin: 0 8px; + padding-left: 5px; + padding-right: 5px; +} +table * { + margin: 0; +} +a:link, a:visited { + text-decoration: underline; + color: #AF0A0F; +} +a:link:hover, a:visited:hover { + color: #F00; +} +a.post_no { + color: inherit; + text-decoration: none; + margin: 0; + padding: 0; +} +.intro a.post_no, p.intro a.email { + margin: 0; +} +.intro a.email span.name { + color: #34345C; +} +.intro a.email:hover span.name { + color: #F00; +} +.intro label { + display: inline; +} +h2 { + color: #AF0A0F; + font-size: 11pt; + margin: 0px; + padding: 0px; +} +h1 { + font-family: tahoma, sans-serif; + letter-spacing: -2px; + font-size: 20pt; + margin: 0; +} +h1.logo img { + display: inline; + float: none; + background-image: url('/static/logo_bg.gif'); + background-repeat: no-repeat; + background-position: center; +} +header div.subtitle, h1 { + color: #000; + text-align: center; +} +header div.subtitle { + font-size: 8pt; +} +form { + margin-bottom: 4em; +} +form table { + margin: auto; +} +form table input { + height: auto; +} +input[type="text"], input[type="password"], textarea { + border: 1px solid #a9a9a9; + text-indent: 0px; + text-shadow: none; + text-transform: none; + word-spacing: normal; + padding: 4px; +} +form table tr td { + text-align: left; + margin: 0px; + padding: 0px; +} +form table tr th { + text-align: left; + padding: 4px; +} +form table tr th { + background: #AF0A0F; + color: #FFF; + border: 1px solid #800000; +} +form table tr td div { + text-align: center; + float: left; +} +form table tr td div input { + display: block; + margin: 2px auto 0 auto; +} +form table tr td div label { + font-size: 10px; +} +.unimportant, .unimportant * { + font-size: 10px; +} +p.fileinfo { + display: block; + margin: 0px; + padding-right: 7em; +} +div.banner { + background-color: #AF0A0F; + font-size: 12pt; + font-weight: bold; + text-align: center; + margin: 1em 0; +} +div.banner, div.banner a { + color: white; +} +div.banner a:hover { + color: #EEF2FF; + text-decoration: none; +} +img.banner, img.board_image { + float: none; + margin: 4px auto 0 auto; +} +img { + display: block; + float: left; + margin: 10px 20px; + border: none; +} +div.post img { + padding: 5px; + margin: 5px 20px 0 0; +} +div.post img.icon { + display: inline; + float: none; + margin: 0 5px; + padding: 0; +} +div.post.op { + margin-right: 20px; + margin-bottom: 5px; +} +div.post.op hr { + border-color: #D9BFB7; +} +.intro { + margin: 0.5em 0; + padding: 0; + padding-bottom: 0.2em; +} +input.delete { + float: left; + margin: 1px 6px 0 0; +} +.intro span.subject { + color: #0F0C5D; + font-weight: bold; +} +.intro span.name { + color: #117743; + font-weight: bold; +} +.intro a.capcode, p.intro a.nametag { + color: #F00000; + margin-left: 0; +} +.intro a { + margin-left: 8px; +} +div.delete { + float: right; +} +div.post.reply p { + margin: 0.3em 0 0 0; +} +div.post.reply div.body { + margin-left: 1.8em; + margin-top: 0.8em; + padding-right: 3em; + padding-bottom: 0.3em; +} +div.post.reply.highlighted { + background-color: #DCDCDC; + border-color: #999; +} +div.post.reply div.body a { + color: #D00; +} +div.post { + max-width: 97%; +} +div.post div.body { + word-wrap: break-word; +} +div.post.reply { + background: #EEE; + margin: 0.2em 16px; + padding: 0.2em 0.3em 0.5em 0.6em; + border-width: 1px; + border-style: none solid solid none; + border-color: #DCDCDC; + display: inline-block; +} +span.trip { + color: #228854; +} +span.quote { + color: #3C6D8A; +} +span.omitted { + display: block; + margin-top: 1em; +} +br.clear { + clear: left; +} +span.controls { + float: right; + margin: 0; + padding: 0; + font-size: 80%; +} +span.controls.op { + float: none; + margin-left: 10px; +} +span.controls a { + margin: 0; +} +div#wrap { + width: 900px; + margin:0 auto; +} +div.ban { + background-color: white; + border: 1px solid #AF0A0F; + max-width: 700px; + margin: 30px auto; +} +div.ban p, div.ban h2 { + padding: 3px 7px; +} +div.ban h2 { + background: #AF0A0F; + color: #FFF; + font-size: 12pt; +} +div.ban p { + font-size: 12px; + margin-bottom: 12px; +} +div.ban p.reason { + font-weight: bold; +} +span.heading { + color: #000; + font-size: 11pt; + font-weight: bold; + display: block; +} +span.spoiler { + background: black; + color: black; +} +div.post.reply div.body span.spoiler a { + color: black; +} +span.spoiler:hover, div.post.reply div.body span.spoiler:hover a { + color: white; +} +div.styles { + float: right; + padding-bottom: 20px; +} +div.styles a { + margin: 0 10px; +} +div.styles a.selected { + text-decoration: none; +} +table.test { + width: 100%; +} +table.test td, table.test th { + text-align: left; + padding: 5px; +} +table.test tr.h th { + background: #AF0A0F; +} +table.test td img { + margin: 0; +} +fieldset label { + display: block; +} +div.pages { + color: #999; + background: #EEE; + display: inline; + padding: 8px; + border-right: 1px solid #DCDCDC; + border-bottom: 1px solid #DCDCDC; +} +div.pages a.selected { + color: black; + font-weight: bolder; +} +div.pages a:link { + text-decoration: none; +} +div.pages form { + margin: 0; + padding: 0; + display: inline; +} +div.pages form input { + margin: 0 5px; + display: inline; +} +hr { + border: none; + border-top: 1px solid #DCDCDC; + height: 0px; +} +div.boardlist { + color: #999; + font-size: 9pt; + margin-top: 3px; +} +div.boardlist.bottom { + margin-top: 20px; +} +div.boardlist a { + text-decoration: none; + color: #AF0A0F; +} +div.report { + color: #333; +} +table.modlog { + margin: auto; + width: 100%; +} +table.modlog tr td { + text-align: left; + margin: 0px; + padding: 4px 15px 0 0; +} +table.modlog tr th { + text-align: left; + padding: 4px 15px 5px 5px; + white-space: nowrap; +} +table.modlog tr th { + background: #AF0A0F; +} +td.minimal, th.minimal { + width: 1%; + white-space: nowrap; +} +div.top_notice { + text-align: center; + margin: 5px auto; +} +span.public_ban { + display: block; + color: red; + font-weight: bold; + margin-top: 15px; +} +span.toolong { + display: block; + margin-top: 15px; +} +div.blotter { + color: red; + font-weight: bold; + text-align: center; +} + +.desktop-style div.boardlist:nth-child(1) { + +} + +.desktop-style div.boardlist:nth-child(1):hover, .desktop-style div.boardlist:nth-child(1).cb-menu { + background-color: rgba(80%, 80%, 80%, 0.35); +} diff --git a/stylesheets/style.css b/stylesheets/style.css index 9925ccaa..23d3ad7d 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -8,6 +8,10 @@ body { padding-right: 4px; } +.hidden { + display:none; +} + a,a:visited { text-decoration: underline; color: #34345C; @@ -194,6 +198,10 @@ img.banner,img.board_image { border: none; } +.full-image { + max-width: 98%; +} + div.post .post-image { padding: 5px; margin: 0 20px 0 0; @@ -412,12 +420,13 @@ fieldset label { } div.pages { - color: #89A; - background: #D6DAF0; - display: inline; - padding: 8px; - border-right: 1px solid #B7C5D9; - border-bottom: 1px solid #B7C5D9; + color: #89A; + background: #D6DAF0; + display: inline-block; + padding: 8px; + margin: 8px 0 4px 0; + border-right: 1px solid #B7C5D9; + border-bottom: 1px solid #B7C5D9; } div.pages.top { @@ -463,20 +472,6 @@ hr { clear: left; } -div.boardlist { - color: #89A; - font-size: 9pt; - margin-top: 3px; -} - -div.boardlist.bottom { - margin-top: 20px; -} - -div.boardlist a { - text-decoration: none; -} - div.report { color: #333; } @@ -661,7 +656,7 @@ pre { margin-left: -20px; } -div.thread:hover { +.theme-catalog div.thread:hover { background: #D6DAF0; border-color: #B7C5D9; } @@ -824,10 +819,14 @@ div.thread:hover { } #options_div { - width: 600px; - height: 320px; + width: 620px; + height: 400px; + resize: both; + overflow: auto; } + + #alert_div { width: 500px; } @@ -858,7 +857,7 @@ div.thread:hover { #options_tablist { padding: 0px 5px; left: 0px; - width: 70px; + width: 90px; top: 0px; bottom: 0px; height: 100%; @@ -887,8 +886,8 @@ div.thread:hover { padding: 10px; position: absolute; top: 0px; - bottom: 0px; - left: 81px; + bottom: 10px; + left: 101px; right: 0px; text-align: left; font-size: 12px; @@ -913,6 +912,18 @@ div.thread:hover { .poster_id { cursor: pointer; + display: inline-block; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +.poster_id:hover { + color: #800000!important; +} +.poster_id::before { + content: " ID: "; } pre { @@ -940,6 +951,78 @@ span.pln { } } +.clearfix { + display: block; + clear: both; + visibility: hidden; + overflow: hidden; + + font-size: 0px; + line-height: 0px; + + box-sizing: border-box; + border: none; + height: 0; + margin: 0; + padding: 0; + width: 100%; + zoom: 1; +} + +/* === SPECIFIC PAGES & FEATURES === */ + +/* Board List */ +div.boardlist { + margin-top: 3px; + + color: #89A; + font-size: 9pt; +} +div.boardlist.bottom { + margin-top: 12px; + clear: both; +} +div.boardlist a { + text-decoration: none; +} + +/* Threads */ +/* Thread Footer */ +#thread-interactions { + margin: 8px 0; + clear: both; +} +#thread-links { + float: left; +} +#thread-links > a { + padding-left: none; + padding-right: 10px; +} +#thread-quick-reply { + display: none; + position: absolute; + left: 50%; + right: 50%; + text-align: center; + width: 100px; + margin-left: -50px; +} +#thread_stats { + float: right; +} + +#post-moderation-fields { + float: right; + text-align: right; +} +#delete-fields { +} +#report-fields { +} + + + /* threadwatcher */ #watchlist { @@ -1115,3 +1198,11 @@ table.fileboard .intro a { max-height: 100%; position: absolute; } +.own_post { + font-style: italic; + font-weight: normal; + opacity: .666; +} +div.mix { + display: inline-block; +} diff --git a/templates/generic_page.html b/templates/generic_page.html index 965cbc2c..fbfc3c50 100644 --- a/templates/generic_page.html +++ b/templates/generic_page.html @@ -6,7 +6,7 @@ {{ board.url }} - {{ board.name }} {% endblock %} - + {{ boardlist.top }} {% if pm %}
You have an unread PM{% if pm.waiting > 0 %}, plus {{ pm.waiting }} more waiting{% endif %}.

{% endif %} {% if config.url_banner %}{% endif %} @@ -36,10 +36,10 @@ {% endfor %} {{ btn.next }} {{ boardlist.bottom }}
-

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

+

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

{% for footer in config.footer %}

{{ footer }}

{% endfor %}
{% endfor %} {% endif %} {% endif %} - {% if config.recaptcha %}