Browse Source

Merge branch 'master' of https://github.com/savetheinternet/Tinyboard into vichan-devel-4.5

Conflicts:
	inc/config.php
	install.php
	post.php
	stylesheets/style.css
pull/40/head
czaks 10 years ago
parent
commit
f5657caf24
  1. 9
      README.md
  2. 47
      inc/config.php
  3. 2
      inc/filters.php
  4. 56
      inc/functions.php
  5. 5
      inc/lib/Twig/Compiler.php
  6. 14
      inc/lib/Twig/Environment.php
  7. 6
      inc/lib/Twig/Error.php
  8. 81
      inc/lib/Twig/ExpressionParser.php
  9. 22
      inc/lib/Twig/Extension/Core.php
  10. 12
      inc/lib/Twig/Extension/StringLoader.php
  11. 7
      inc/lib/Twig/Loader/Array.php
  12. 3
      inc/lib/Twig/Loader/Chain.php
  13. 27
      inc/lib/Twig/Loader/Filesystem.php
  14. 6
      inc/lib/Twig/Node/Expression/Call.php
  15. 4
      inc/lib/Twig/Node/Expression/GetAttr.php
  16. 60
      inc/lib/Twig/Node/Expression/MacroCall.php
  17. 4
      inc/lib/Twig/Node/Macro.php
  18. 34
      inc/lib/Twig/Node/Module.php
  19. 2
      inc/lib/Twig/NodeVisitor/SafeAnalysis.php
  20. 2
      inc/lib/Twig/Parser.php
  21. 74
      inc/lib/Twig/Template.php
  22. 2
      inc/lib/Twig/TokenParser/From.php
  23. 201
      inc/mod/pages.php
  24. 19
      install.php
  25. 16
      install.sql
  26. 12
      js/quick-reply.js
  27. 86
      mod.php
  28. 44
      post.php
  29. 7
      stylesheets/style.css
  30. 56
      templates/banned.html
  31. 107
      templates/mod/ban_appeals.html
  32. 3
      templates/mod/ban_list.html
  33. 1
      templates/mod/board.html
  34. 1
      templates/mod/config-editor-php.html
  35. 1
      templates/mod/config-editor.html
  36. 5
      templates/mod/dashboard.html
  37. 18
      templates/mod/debug/apc.html
  38. 1
      templates/mod/new_pm.html
  39. 3
      templates/mod/news.html
  40. 5
      templates/mod/noticeboard.html
  41. 1
      templates/mod/rebuild.html
  42. 4
      templates/mod/report.html
  43. 1
      templates/mod/theme_config.html
  44. 4
      templates/mod/themes.html
  45. 1
      templates/mod/user.html
  46. 4
      templates/mod/users.html
  47. 2
      templates/mod/view_ip.html

9
README.md

@ -17,7 +17,7 @@ imageboard software package. It is written in PHP and has few dependencies.
Requirements
------------
1. PHP >= 5.2.5
1. PHP >= 5.3
2. MySQL server
3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php)
4. [PHP GD](http://www.php.net/manual/en/intro.image.php)
@ -28,10 +28,9 @@ operating systems. Tinyboard does not include an Apache ```.htaccess``` file nor
it need one.
### Recommended
1. PHP >= 5.3
2. MySQL server >= 5.5.3
3. ImageMagick (command-line ImageMagick or GraphicsMagick preferred).
4. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php), [XCache](http://xcache.lighttpd.net/) or [Memcached](http://www.php.net/manual/en/intro.memcached.php)
1. MySQL server >= 5.5.3
2. ImageMagick (command-line ImageMagick or GraphicsMagick preferred).
3. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php), [XCache](http://xcache.lighttpd.net/) or [Memcached](http://www.php.net/manual/en/intro.memcached.php)
Contributing
------------

47
inc/config.php

@ -528,9 +528,31 @@
// pure-PHP geolocation library.
$config['country_flags'] = false;
/*
* ====================
* Ban settings
* ====================
*/
// Require users to see the ban page at least once for a ban even if it has since expired.
$config['require_ban_view'] = true;
// Show the post the user was banned for on the "You are banned" page.
$config['ban_show_post'] = false;
// Optional HTML to append to "You are banned" pages. For example, you could include instructions and/or
// a link to an email address or IRC chat room to appeal the ban.
$config['ban_page_extra'] = '';
// Allow users to appeal bans through Tinyboard.
$config['ban_appeals'] = false;
// Do not allow users to appeal bans that are shorter than this length (in seconds).
$config['ban_appeals_min_length'] = 60 * 60 * 6; // 6 hours
// How many ban appeals can be made for a single ban?
$config['ban_appeals_max'] = 1;
/*
* ====================
* Markup settings
@ -854,13 +876,6 @@
// 'bottom' => '',
// );
// Show the post the user was banned for on the "You are banned" page.
$config['ban_show_post'] = false;
// Optional HTML to append to "You are banned" pages. For example, you could include instructions and/or
// a link to an email address or IRC chat room to appeal the ban.
$config['ban_page_extra'] = '';
// Display flags (when available). This config option has no effect unless poster flags are enabled (see
// $config['country_flags']). Disable this if you want all previously-assigned flags to be hidden.
$config['display_flags'] = true;
@ -954,7 +969,6 @@
*/
// Error messages
$config['error']['lurk'] = _('Lurk some more before posting.');
$config['error']['bot'] = _('You look like a bot.');
$config['error']['referer'] = _('Your browser sent an invalid or no HTTP referer.');
$config['error']['toolong'] = _('The %s field was too long.');
@ -1019,9 +1033,14 @@
// The root directory, including the trailing slash, for Tinyboard.
// Examples: '/', 'http://boards.chan.org/', '/chan/'.
if (isset($_SERVER['REQUEST_URI']))
$config['root'] = str_replace('\\', '/', dirname($_SERVER['REQUEST_URI'])) == '/' ? '/' : str_replace('\\', '/', dirname($_SERVER['REQUEST_URI'])) . '/';
else
if (isset($_SERVER['REQUEST_URI'])) {
$request_uri = $_SERVER['REQUEST_URI'];
if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] !== '')
$request_uri = substr($request_uri, 0, - 1 - strlen($_SERVER['QUERY_STRING']));
$config['root'] = str_replace('\\', '/', dirname($request_uri)) == '/'
? '/' : str_replace('\\', '/', dirname($request_uri)) . '/';
unset($request_uri);
} else
$config['root'] = '/'; // CLI mode
// The scheme and domain. This is used to get the site's absolute URL (eg. for image identification links).
@ -1358,8 +1377,14 @@
$config['mod']['news_delete'] = ADMIN;
// Execute un-filtered SQL queries on the database (?/debug/sql)
$config['mod']['debug_sql'] = DISABLED;
// Look through all cache values for debugging when APC is enabled (?/debug/apc)
$config['mod']['debug_apc'] = ADMIN;
// Edit the current configuration (via web interface)
$config['mod']['edit_config'] = ADMIN;
// View ban appeals
$config['mod']['view_ban_appeals'] = MOD;
// Accept and deny ban appeals
$config['mod']['ban_appeals'] = MOD;
// Config editor permissions
$config['mod']['config'] = array();

2
inc/filters.php

@ -91,6 +91,8 @@ class Filter {
return preg_match($match, $post['subject']);
case 'body':
return preg_match($match, $post['body_nomarkup']);
case 'filehash':
return $match === $post['filehash'];
case 'filename':
if (!$post['has_file'])
return false;

56
inc/functions.php

@ -628,11 +628,16 @@ function displayBan($ban) {
$ban['ip'] = $_SERVER['REMOTE_ADDR'];
if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
openBoard($ban['post']['board']);
$query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " . (int)$ban['post']['id'], $board['uri']));
if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
$ban['post'] = array_merge($ban['post'], $_post);
if (openBoard($ban['post']['board'])) {
$query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " .
(int)$ban['post']['id'], $board['uri']));
if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
$ban['post'] = array_merge($ban['post'], $_post);
} else {
$ban['post']['file'] = 'deleted';
$ban['post']['thumb'] = false;
}
} else {
$ban['post']['file'] = 'deleted';
$ban['post']['thumb'] = false;
@ -644,6 +649,21 @@ function displayBan($ban) {
$post = new Thread($ban['post'], null, false, false);
}
}
$denied_appeals = array();
$pending_appeal = false;
if ($config['ban_appeals']) {
$query = query("SELECT `time`, `denied` FROM `ban_appeals` WHERE `ban_id` = " . (int)$ban['id']) or error(db_error());
while ($ban_appeal = $query->fetch(PDO::FETCH_ASSOC)) {
if ($ban_appeal['denied']) {
$denied_appeals[] = $ban_appeal['time'];
} else {
$pending_appeal = $ban_appeal['time'];
}
}
}
// Show banned page and exit
die(
Element('page.html', array(
@ -654,7 +674,9 @@ function displayBan($ban) {
'config' => $config,
'ban' => $ban,
'board' => $board,
'post' => isset($post) ? $post->build(true) : false
'post' => isset($post) ? $post->build(true) : false,
'denied_appeals' => $denied_appeals,
'pending_appeal' => $pending_appeal
)
))
));
@ -1524,7 +1546,25 @@ function markup_url($matches) {
$markup_urls[] = $url;
return '<a target="_blank" rel="nofollow" href="'. $config['link_prefix'] . $url . '">' . $url . '</a>' . $after;
$link = (object) array(
'href' => $url,
'text' => $url,
'rel' => 'nofollow',
'target' => '_blank',
);
event('markup-url', $link);
$link = (array)$link;
$parts = array();
foreach ($link as $attr => $value) {
if ($attr == 'text' || $attr == 'after')
continue;
$parts[] = $attr . '="' . htmlspecialchars($value) . '"';
}
if (isset($link['after']))
$after = $link['after'] . $after;
return '<a ' . implode(' ', $parts) . '>' . utf8tohtml($link['text']) . '</a>' . $after;
}
function unicodify($body) {
@ -2049,7 +2089,7 @@ function generate_tripcode($name) {
if (isset($config['custom_tripcode']["##{$trip}"]))
$trip = $config['custom_tripcode']["##{$trip}"];
else
$trip = '!!' . substr(crypt($trip, $config['secure_trip_salt']), -10);
$trip = '!!' . substr(crypt($trip, '_..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}"];

5
inc/lib/Twig/Compiler.php

@ -180,11 +180,12 @@ class Twig_Compiler implements Twig_CompilerInterface
$this->raw($value ? 'true' : 'false');
} elseif (is_array($value)) {
$this->raw('array(');
$i = 0;
$first = true;
foreach ($value as $key => $value) {
if ($i++) {
if (!$first) {
$this->raw(', ');
}
$first = false;
$this->repr($key);
$this->raw(' => ');
$this->repr($value);

14
inc/lib/Twig/Environment.php

@ -16,7 +16,7 @@
*/
class Twig_Environment
{
const VERSION = '1.13.1';
const VERSION = '1.14.0-DEV';
protected $charset;
protected $loader;
@ -44,6 +44,7 @@ class Twig_Environment
protected $functionCallbacks;
protected $filterCallbacks;
protected $staging;
protected $templateClasses;
/**
* Constructor.
@ -107,6 +108,7 @@ class Twig_Environment
$this->setCache($options['cache']);
$this->functionCallbacks = array();
$this->filterCallbacks = array();
$this->templateClasses = array();
$this->addExtension(new Twig_Extension_Core());
$this->addExtension(new Twig_Extension_Escaper($options['autoescape']));
@ -262,7 +264,13 @@ class Twig_Environment
*/
public function getTemplateClass($name, $index = null)
{
return $this->templateClassPrefix.md5($this->getLoader()->getCacheKey($name)).(null === $index ? '' : '_'.$index);
$suffix = null === $index ? '' : '_'.$index;
$cls = $name.$suffix;
if (isset($this->templateClasses[$cls])) {
return $this->templateClasses[$cls];
}
return $this->templateClasses[$cls] = $this->templateClassPrefix.hash('sha256', $this->getLoader()->getCacheKey($name)).$suffix;
}
/**
@ -728,7 +736,7 @@ class Twig_Environment
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{
if ($this->extensionInitialized) {
throw new LogicException('Unable to add a node visitor as extensions have already been initialized.', $extension->getName());
throw new LogicException('Unable to add a node visitor as extensions have already been initialized.');
}
$this->staging->addNodeVisitor($visitor);

6
inc/lib/Twig/Error.php

@ -186,6 +186,7 @@ class Twig_Error extends Exception
protected function guessTemplateInfo()
{
$template = null;
$templateClass = null;
if (version_compare(phpversion(), '5.3.6', '>=')) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
@ -195,8 +196,11 @@ class Twig_Error extends Exception
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
if (null === $this->filename || $this->filename == $trace['object']->getTemplateName()) {
$currentClass = get_class($trace['object']);
$isEmbedContainer = 0 === strpos($templateClass, $currentClass);
if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object'];
$templateClass = get_class($trace['object']);
}
}
}

81
inc/lib/Twig/ExpressionParser.php

@ -316,23 +316,23 @@ class Twig_ExpressionParser
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line);
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_Template::ANY_CALL, $line);
default:
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
$arguments = new Twig_Node_Expression_Array(array(), $line);
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
$args = $this->parseArguments(true);
if (null !== $alias = $this->parser->getImportedSymbol('macro', $name)) {
return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $this->createArrayFromArguments($args), $line);
}
$node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
$node->setAttribute('safe', true);
try {
$class = $this->getFunctionNodeClass($name, $line);
} catch (Twig_Error_Syntax $e) {
if (!$this->parser->hasMacro($name)) {
throw $e;
}
return $node;
return new Twig_Node_Expression_MacroCall(new Twig_Node_Expression_Name('_self', $line), $name, $this->createArrayFromArguments($args), $line);
}
$args = $this->parseArguments(true);
$class = $this->getFunctionNodeClass($name, $line);
return new $class($name, $args, $line);
}
}
@ -343,7 +343,7 @@ class Twig_ExpressionParser
$token = $stream->next();
$lineno = $token->getLine();
$arguments = new Twig_Node_Expression_Array(array(), $lineno);
$type = Twig_TemplateInterface::ANY_CALL;
$type = Twig_Template::ANY_CALL;
if ($token->getValue() == '.') {
$token = $stream->next();
if (
@ -354,13 +354,6 @@ class Twig_ExpressionParser
($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
) {
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$type = Twig_TemplateInterface::METHOD_CALL;
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
}
} else {
throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
}
@ -370,13 +363,17 @@ class Twig_ExpressionParser
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
}
$node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
$node->setAttribute('safe', true);
$arguments = $this->createArrayFromArguments($this->parseArguments(true));
return new Twig_Node_Expression_MacroCall($node, $arg->getAttribute('value'), $arguments, $lineno);
}
return $node;
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$type = Twig_Template::METHOD_CALL;
$arguments = $this->createArrayFromArguments($this->parseArguments());
}
} else {
$type = Twig_TemplateInterface::ARRAY_CALL;
$type = Twig_Template::ARRAY_CALL;
// slice?
$slice = false;
@ -452,6 +449,8 @@ class Twig_ExpressionParser
*
* @param Boolean $namedArguments Whether to allow named arguments or not
* @param Boolean $definition Whether we are parsing arguments for a function definition
*
* @return Twig_Node
*/
public function parseArguments($namedArguments = false, $definition = false)
{
@ -483,25 +482,26 @@ class Twig_ExpressionParser
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename());
throw new Twig_Error_Syntax('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $this->parser->getFilename());
}
} else {
$value = $this->parseExpression();
}
}
if ($definition) {
if (null === $name) {
$name = $value->getAttribute('name');
$value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
}
$args[$name] = $value;
if ($definition && null === $name) {
$name = $value->getAttribute('name');
$value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
}
if (null === $name) {
$args[] = $value;
} else {
if (null === $name) {
$args[] = $value;
} else {
$args[$name] = $value;
if ($definition && isset($args[$name])) {
throw new Twig_Error_Syntax(sprintf('Arguments cannot contain the same argument name more than once ("%s" is defined twice).', $name), $token->getLine(), $this->parser->getFilename());
}
$args[$name] = $value;
}
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
@ -597,4 +597,15 @@ class Twig_ExpressionParser
return true;
}
private function createArrayFromArguments(Twig_Node $arguments, $line = null)
{
$line = null === $line ? $arguments->getLine() : $line;
$array = new Twig_Node_Expression_Array(array(), $line);
foreach ($arguments as $key => $value) {
$array->addElement($value, new Twig_Node_Expression_Constant($key, $value->getLine()));
}
return $array;
}
}

22
inc/lib/Twig/Extension/Core.php

@ -348,7 +348,7 @@ function twig_random(Twig_Environment $env, $values = null)
return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
}
if ($values instanceof Traversable) {
if (is_object($values) && $values instanceof Traversable) {
$values = iterator_to_array($values);
} elseif (is_string($values)) {
if ('' === $values) {
@ -620,7 +620,7 @@ function twig_array_merge($arr1, $arr2)
*/
function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
{
if ($item instanceof Traversable) {
if (is_object($item) && $item instanceof Traversable) {
$item = iterator_to_array($item, false);
}
@ -687,7 +687,7 @@ function twig_last(Twig_Environment $env, $item)
*/
function twig_join_filter($value, $glue = '')
{
if ($value instanceof Traversable) {
if (is_object($value) && $value instanceof Traversable) {
$value = iterator_to_array($value, false);
}
@ -829,7 +829,7 @@ function twig_in_filter($value, $compare)
}
return false !== strpos($compare, (string) $value);
} elseif ($compare instanceof Traversable) {
} elseif (is_object($compare) && $compare instanceof Traversable) {
return in_array($value, iterator_to_array($compare, false), is_object($value));
}
@ -1329,13 +1329,13 @@ function twig_constant($constant, $object = null)
*
* @param array $items An array of items
* @param integer $size The size of the batch
* @param string $fill A string to fill missing items
* @param mixed $fill A value used to fill missing items
*
* @return array
*/
function twig_array_batch($items, $size, $fill = null)
{
if ($items instanceof Traversable) {
if (is_object($items) && $items instanceof Traversable) {
$items = iterator_to_array($items, false);
}
@ -1345,10 +1345,12 @@ function twig_array_batch($items, $size, $fill = null)
if (null !== $fill) {
$last = count($result) - 1;
$result[$last] = array_merge(
$result[$last],
array_fill(0, $size - count($result[$last]), $fill)
);
if ($fillCount = $size - count($result[$last])) {
$result[$last] = array_merge(
$result[$last],
array_fill(0, $fillCount, $fill)
);
}
}
return $result;

12
inc/lib/Twig/Extension/StringLoader.php

@ -43,16 +43,16 @@ class Twig_Extension_StringLoader extends Twig_Extension
*/
function twig_template_from_string(Twig_Environment $env, $template)
{
static $loader;
$name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false));
if (null === $loader) {
$loader = new Twig_Loader_String();
}
$loader = new Twig_Loader_Chain(array(
new Twig_Loader_Array(array($name => $template)),
$current = $env->getLoader(),
));
$current = $env->getLoader();
$env->setLoader($loader);
try {
$template = $env->loadTemplate($template);
$template = $env->loadTemplate($name);
} catch (Exception $e) {
$env->setLoader($current);

7
inc/lib/Twig/Loader/Array.php

@ -21,7 +21,7 @@
*/
class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
protected $templates;
protected $templates = array();
/**
* Constructor.
@ -32,10 +32,7 @@ class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterf
*/
public function __construct(array $templates)
{
$this->templates = array();
foreach ($templates as $name => $template) {
$this->templates[$name] = $template;
}
$this->templates = $templates;
}
/**

3
inc/lib/Twig/Loader/Chain.php

@ -17,7 +17,7 @@
class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
private $hasSourceCache = array();
protected $loaders;
protected $loaders = array();
/**
* Constructor.
@ -26,7 +26,6 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf
*/
public function __construct(array $loaders = array())
{
$this->loaders = array();
foreach ($loaders as $loader) {
$this->addLoader($loader);
}

27
inc/lib/Twig/Loader/Filesystem.php

@ -16,8 +16,11 @@
*/
class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
protected $paths;
protected $cache;
/** Identifier of the main namespace. */
const MAIN_NAMESPACE = '__main__';
protected $paths = array();
protected $cache = array();
/**
* Constructor.
@ -38,7 +41,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
*
* @return array The array of paths where to look for templates
*/
public function getPaths($namespace = '__main__')
public function getPaths($namespace = self::MAIN_NAMESPACE)
{
return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array();
}
@ -46,7 +49,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
/**
* Returns the path namespaces.
*
* The "__main__" namespace is always defined.
* The main namespace is always defined.
*
* @return array The array of defined namespaces
*/
@ -61,7 +64,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
* @param string|array $paths A path or an array of paths where to look for templates
* @param string $namespace A path namespace
*/
public function setPaths($paths, $namespace = '__main__')
public function setPaths($paths, $namespace = self::MAIN_NAMESPACE)
{
if (!is_array($paths)) {
$paths = array($paths);
@ -81,7 +84,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
*
* @throws Twig_Error_Loader
*/
public function addPath($path, $namespace = '__main__')
public function addPath($path, $namespace = self::MAIN_NAMESPACE)
{
// invalidate the cache
$this->cache = array();
@ -101,7 +104,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
*
* @throws Twig_Error_Loader
*/
public function prependPath($path, $namespace = '__main__')
public function prependPath($path, $namespace = self::MAIN_NAMESPACE)
{
// invalidate the cache
$this->cache = array();
@ -175,15 +178,15 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
$this->validateName($name);
$namespace = '__main__';
$namespace = self::MAIN_NAMESPACE;
$shortname = $name;
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
}
$namespace = substr($name, 1, $pos - 1);
$name = substr($name, $pos + 1);
$shortname = substr($name, $pos + 1);
}
if (!isset($this->paths[$namespace])) {
@ -191,8 +194,8 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
}
foreach ($this->paths[$namespace] as $path) {
if (is_file($path.'/'.$name)) {
return $this->cache[$name] = $path.'/'.$name;
if (is_file($path.'/'.$shortname)) {
return $this->cache[$name] = $path.'/'.$shortname;
}
}

6
inc/lib/Twig/Node/Expression/Call.php

@ -146,7 +146,7 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
if (array_key_exists($name, $parameters)) {
if (array_key_exists($pos, $parameters)) {
throw new Twig_Error_Syntax(sprintf('Arguments "%s" is defined twice for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
}
$arguments[] = $parameters[$name];
@ -164,8 +164,8 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
}
}
foreach (array_keys($parameters) as $name) {
throw new Twig_Error_Syntax(sprintf('Unknown argument "%s" for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
if (!empty($parameters)) {
throw new Twig_Error_Syntax(sprintf('Unknown argument%s "%s" for %s "%s".', count($parameters) > 1 ? 's' : '' , implode('", "', array_keys($parameters)), $this->getAttribute('type'), $this->getAttribute('name')));
}
return $arguments;

4
inc/lib/Twig/Node/Expression/GetAttr.php

@ -32,10 +32,10 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
$compiler->raw(', ')->subcompile($this->getNode('attribute'));
if (count($this->getNode('arguments')) || Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
if (count($this->getNode('arguments')) || Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
if (Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
if (Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->repr($this->getAttribute('type'));
}

60
inc/lib/Twig/Node/Expression/MacroCall.php

@ -0,0 +1,60 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a macro call node.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class Twig_Node_Expression_MacroCall extends Twig_Node_Expression
{
public function __construct(Twig_Node_Expression $template, $name, Twig_Node_Expression_Array $arguments, $lineno)
{
parent::__construct(array('template' => $template, 'arguments' => $arguments), array('name' => $name), $lineno);
}
public function compile(Twig_Compiler $compiler)
{
$namedNames = array();
$namedCount = 0;
$positionalCount = 0;
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
$name = $pair['key']->getAttribute('value');
if (!is_int($name)) {
$namedCount++;
$namedNames[$name] = 1;
} elseif ($namedCount > 0) {
throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for macro "%s".', $this->getAttribute('name')), $this->lineno);
} else {
$positionalCount++;
}
}
$compiler
->raw('$this->callMacro(')
->subcompile($this->getNode('template'))
->raw(', ')->repr($this->getAttribute('name'))
->raw(', ')->subcompile($this->getNode('arguments'))
;
if ($namedCount > 0) {
$compiler
->raw(', ')->repr($namedNames)
->raw(', ')->repr($namedCount)
->raw(', ')->repr($positionalCount)
;
}
$compiler
->raw(')')
;
}
}

4
inc/lib/Twig/Node/Macro.php

@ -18,7 +18,7 @@ class Twig_Node_Macro extends Twig_Node
{
public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
{
parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag);
parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name, 'method' => 'get'.ucfirst($name)), $lineno, $tag);
}
/**
@ -30,7 +30,7 @@ class Twig_Node_Macro extends Twig_Node
{
$compiler
->addDebugInfo($this)
->write(sprintf("public function get%s(", $this->getAttribute('name')))
->write(sprintf("public function %s(", $this->getAttribute('method')))
;
$count = count($this->getNode('arguments'));

34
inc/lib/Twig/Node/Module.php

@ -233,11 +233,43 @@ class Twig_Node_Module extends Twig_Node
;
}
$compiler
->outdent()
->write(");\n\n")
;
// macro information
$compiler
->write("\$this->macros = array(\n")
->indent()
;
foreach ($this->getNode('macros') as $name => $node) {
$compiler
->addIndentation()->repr($name)->raw(" => array(\n")
->indent()
->write("'method' => ")->repr($node->getAttribute('method'))->raw(",\n")
->write("'arguments' => array(\n")
->indent()
;
foreach ($node->getNode('arguments') as $argument => $value) {
$compiler->addIndentation()->repr($argument)->raw (' => ')->subcompile($value)->raw(",\n");
}
$compiler
->outdent()
->write("),\n")
->outdent()
->write("),\n")
;
}
$compiler
->outdent()
->write(");\n")
;
$compiler
->outdent()
->write("}\n\n");
->write("}\n\n")
;
}

2
inc/lib/Twig/NodeVisitor/SafeAnalysis.php

@ -89,6 +89,8 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
} else {
$this->setSafe($node, array());
}
} elseif ($node instanceof Twig_Node_Expression_MacroCall) {
$this->setSafe($node, array('all'));
} elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) {
$name = $node->getNode('node')->getAttribute('name');
// attributes on template instances are safe

2
inc/lib/Twig/Parser.php

@ -49,7 +49,7 @@ class Twig_Parser implements Twig_ParserInterface
public function getVarName()
{
return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false));
return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false));
}
public function getFilename()

74
inc/lib/Twig/Template.php

@ -24,6 +24,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
protected $env;
protected $blocks;
protected $traits;
protected $macros;
/**
* Constructor.
@ -35,6 +36,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
$this->env = $env;
$this->blocks = array();
$this->traits = array();
$this->macros = array();
}
/**
@ -326,7 +328,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
* @param mixed $object The object or array from where to get the item
* @param mixed $item The item to get from the array or object
* @param array $arguments An array of arguments to pass if the item is an object method
* @param string $type The type of attribute (@see Twig_TemplateInterface)
* @param string $type The type of attribute (@see Twig_Template constants)
* @param Boolean $isDefinedTest Whether this is only a defined check
* @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not
*
@ -334,10 +336,10 @@ abstract class Twig_Template implements Twig_TemplateInterface
*
* @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
*/
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
{
// array
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if (Twig_Template::METHOD_CALL !== $type) {
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
if ((is_array($object) && array_key_exists($arrayItem, $object))
@ -350,7 +352,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
return $object[$arrayItem];
}
if (Twig_TemplateInterface::ARRAY_CALL === $type || !is_object($object)) {
if (Twig_Template::ARRAY_CALL === $type || !is_object($object)) {
if ($isDefinedTest) {
return false;
}
@ -363,7 +365,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $arrayItem, get_class($object)), -1, $this->getTemplateName());
} elseif (is_array($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))), -1, $this->getTemplateName());
} elseif (Twig_TemplateInterface::ARRAY_CALL === $type) {
} elseif (Twig_Template::ARRAY_CALL === $type) {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} else {
throw new Twig_Error_Runtime(sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
@ -386,7 +388,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
$class = get_class($object);
// object property
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if (Twig_Template::METHOD_CALL !== $type) {
if (isset($object->$item) || array_key_exists((string) $item, $object)) {
if ($isDefinedTest) {
return true;
@ -445,6 +447,66 @@ abstract class Twig_Template implements Twig_TemplateInterface
return $ret;
}
/**
* Calls macro in a template.
*
* @param Twig_Template $template The template
* @param string $macro The name of macro
* @param array $arguments The arguments of macro
* @param array $namedNames An array of names of arguments as keys
* @param integer $namedCount The count of named arguments
* @param integer $positionalCount The count of positional arguments
*
* @return string The content of a macro
*
* @throws Twig_Error_Runtime if the macro is not defined
* @throws Twig_Error_Runtime if the argument is defined twice
* @throws Twig_Error_Runtime if the argument is unknown
*/
protected function callMacro(Twig_Template $template, $macro, array $arguments, array $namedNames = array(), $namedCount = 0, $positionalCount = -1)
{
if (!isset($template->macros[$macro]['reflection'])) {
if (!isset($template->macros[$macro])) {
throw new Twig_Error_Runtime(sprintf('Macro "%s" is not defined in the template "%s".', $macro, $template->getTemplateName()));
}
$template->macros[$macro]['reflection'] = new ReflectionMethod($template, $template->macros[$macro]['method']);
}
if ($namedCount < 1) {
return $template->macros[$macro]['reflection']->invokeArgs($template, $arguments);
}
$i = 0;
$args = array();
foreach ($template->macros[$macro]['arguments'] as $name => $value) {
if (isset($namedNames[$name])) {
if ($i < $positionalCount) {
throw new Twig_Error_Runtime(sprintf('Argument "%s" is defined twice for macro "%s" defined in the template "%s".', $name, $macro, $template->getTemplateName()));
}
$args[] = $arguments[$name];
if (--$namedCount < 1) {
break;
}
} elseif ($i < $positionalCount) {
$args[] = $arguments[$i];
} else {
$args[] = $value;
}
$i++;
}
if ($namedCount > 0) {
$parameters = array_keys(array_diff_key($namedNames, $template->macros[$macro]['arguments']));
throw new Twig_Error_Runtime(sprintf('Unknown argument%s "%s" for macro "%s" defined in the template "%s".', count($parameters) > 1 ? 's' : '' , implode('", "', $parameters), $macro, $template->getTemplateName()));
}
return $template->macros[$macro]['reflection']->invokeArgs($template, $args);
}
/**
* This method is only useful when testing Twig. Do not use it.
*/

2
inc/lib/Twig/TokenParser/From.php

@ -56,7 +56,7 @@ class Twig_TokenParser_From extends Twig_TokenParser
$node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag());
foreach ($targets as $name => $alias) {
$this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var'));
$this->parser->addImportedSymbol('macro', $alias, $name, $node->getNode('var'));
}
return $node;

201
inc/mod/pages.php

@ -156,7 +156,9 @@ function mod_dashboard() {
if ($latest)
$args['newer_release'] = $latest;
}
$args['logout_token'] = make_secure_link_token('logout');
mod_page(_('Dashboard'), 'mod/dashboard.html', $args);
}
@ -210,7 +212,7 @@ function mod_search($type, $search_query_escaped, $page_no = 1) {
// Array of phrases to match
$match = array();
// Exact phrases ("like this")
if (preg_match_all('/"(.+?)"/', $query, $exact_phrases)) {
$exact_phrases = $exact_phrases[1];
@ -230,14 +232,14 @@ function mod_search($type, $search_query_escaped, $page_no = 1) {
// Which `field` to search?
if ($type == 'posts')
$sql_field = array('body_nomarkup', 'filename', 'subject', 'filehash', 'ip', 'name', 'trip');
$sql_field = array('body_nomarkup', 'filename', 'file', 'subject', 'filehash', 'ip', 'name', 'trip');
if ($type == 'IP_notes')
$sql_field = 'body';
if ($type == 'bans')
$sql_field = 'reason';
if ($type == 'log')
$sql_field = 'text';
// Build the "LIKE 'this' AND LIKE 'that'" etc. part of the SQL query
$sql_like = '';
foreach ($match as $phrase) {
@ -254,16 +256,14 @@ function mod_search($type, $search_query_escaped, $page_no = 1) {
}
}
// Compile SQL query
if ($type == 'posts') {
$query = '';
$boards = listBoards();
if (empty($boards))
error(_('There are no boards to search!'));
foreach ($boards as $board) {
openBoard($board['uri']);
if (!hasPermission($config['mod']['search_posts'], $board['uri']))
@ -435,7 +435,10 @@ function mod_edit_board($boardName) {
header('Location: ?/', true, $config['redirect_http']);
} else {
mod_page(sprintf('%s: ' . $config['board_abbreviation'], _('Edit board'), $board['uri']), 'mod/board.html', array('board' => $board));
mod_page(sprintf('%s: ' . $config['board_abbreviation'], _('Edit board'), $board['uri']), 'mod/board.html', array(
'board' => $board,
'token' => make_secure_link_token('edit/' . $board['uri'])
));
}
}
@ -505,7 +508,7 @@ function mod_new_board() {
header('Location: ?/' . $board['uri'] . '/' . $config['file_index'], true, $config['redirect_http']);
}
mod_page(_('New board'), 'mod/board.html', array('new' => true));
mod_page(_('New board'), 'mod/board.html', array('new' => true, 'token' => make_secure_link_token('new-board')));
}
function mod_noticeboard($page_no = 1) {
@ -548,11 +551,19 @@ function mod_noticeboard($page_no = 1) {
if (empty($noticeboard) && $page_no > 1)
error($config['error']['404']);
foreach ($noticeboard as &$entry) {
$entry['delete_token'] = make_secure_link_token('noticeboard/delete/' . $entry['id']);
}
$query = prepare("SELECT COUNT(*) FROM ``noticeboard``");
$query->execute() or error(db_error($query));
$count = $query->fetchColumn();
mod_page(_('Noticeboard'), 'mod/noticeboard.html', array('noticeboard' => $noticeboard, 'count' => $count));
mod_page(_('Noticeboard'), 'mod/noticeboard.html', array(
'noticeboard' => $noticeboard,
'count' => $count,
'token' => make_secure_link_token('noticeboard')
));
}
function mod_noticeboard_delete($id) {
@ -609,11 +620,15 @@ function mod_news($page_no = 1) {
if (empty($news) && $page_no > 1)
error($config['error']['404']);
foreach ($news as &$entry) {
$entry['delete_token'] = make_secure_link_token('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));
mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('news')));
}
function mod_news_delete($id) {
@ -829,6 +844,8 @@ function mod_page_ip($ip) {
$args['logs'] = array();
}
$args['security_token'] = make_secure_link_token('IP/' . $ip);
mod_page(sprintf('%s: %s', _('IP'), $ip), 'mod/view_ip.html', $args, $args['hostname']);
}
@ -891,9 +908,86 @@ function mod_bans($page_no = 1) {
$ban['single_addr'] = true;
}
mod_page(_('Ban list'), 'mod/ban_list.html', array('bans' => $bans, 'count' => Bans::count()));
mod_page(_('Ban list'), 'mod/ban_list.html', array(
'bans' => $bans,
'count' => Bans::count(),
'token' => make_secure_link_token('bans')
));
}
function mod_ban_appeals() {
global $config, $board;
if (!hasPermission($config['mod']['view_ban_appeals']))
error($config['error']['noaccess']);
// Remove stale ban appeals
query("DELETE FROM ``ban_appeals`` WHERE NOT EXISTS (SELECT 1 FROM ``bans`` WHERE `ban_id` = ``bans``.`id`)")
or error(db_error());
if (isset($_POST['appeal_id']) && (isset($_POST['unban']) || isset($_POST['deny']))) {
if (!hasPermission($config['mod']['ban_appeals']))
error($config['error']['noaccess']);
$query = query("SELECT *, ``ban_appeals``.`id` AS `id` FROM ``ban_appeals``
LEFT JOIN ``bans`` ON `ban_id` = ``bans``.`id`
WHERE ``ban_appeals``.`id` = " . (int)$_POST['appeal_id']) or error(db_error());
if (!$ban = $query->fetch(PDO::FETCH_ASSOC)) {
error(_('Ban appeal not found!'));
}
$ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
if (isset($_POST['unban'])) {
modLog('Accepted ban appeal #' . $ban['id'] . ' for ' . $ban['mask']);
Bans::delete($ban['ban_id'], true);
query("DELETE FROM ``ban_appeals`` WHERE `id` = " . $ban['id']) or error(db_error());
} else {
modLog('Denied ban appeal #' . $ban['id'] . ' for ' . $ban['mask']);
query("UPDATE ``ban_appeals`` SET `denied` = 1 WHERE `id` = " . $ban['id']) or error(db_error());
}
header('Location: ?/ban-appeals', true, $config['redirect_http']);
return;
}
$query = query("SELECT *, ``ban_appeals``.`id` AS `id` FROM ``ban_appeals``
LEFT JOIN ``bans`` ON `ban_id` = ``bans``.`id`
WHERE `denied` != 1 ORDER BY `time`") or error(db_error());
$ban_appeals = $query->fetchAll(PDO::FETCH_ASSOC);
foreach ($ban_appeals as &$ban) {
if ($ban['post'])
$ban['post'] = json_decode($ban['post'], true);
$ban['mask'] = Bans::range_to_string(array($ban['ipstart'], $ban['ipend']));
if ($ban['post'] && isset($ban['post']['board'], $ban['post']['id'])) {
if (openBoard($ban['post']['board'])) {
$query = query(sprintf("SELECT `thumb`, `file` FROM ``posts_%s`` WHERE `id` = " .
(int)$ban['post']['id'], $board['uri']));
if ($_post = $query->fetch(PDO::FETCH_ASSOC)) {
$ban['post'] = array_merge($ban['post'], $_post);
} else {
$ban['post']['file'] = 'deleted';
$ban['post']['thumb'] = false;
}
} else {
$ban['post']['file'] = 'deleted';
$ban['post']['thumb'] = false;
}
if ($ban['post']['thread']) {
$ban['post'] = new Post($ban['post']);
} else {
$ban['post'] = new Thread($ban['post'], null, false, false);
}
}
}
mod_page(_('Ban appeals'), 'mod/ban_appeals.html', array(
'ban_appeals' => $ban_appeals,
'token' => make_secure_link_token('ban-appeals')
));
}
function mod_lock($board, $unlock, $post) {
global $config;
@ -1675,7 +1769,12 @@ function mod_user($uid) {
$user['boards'] = explode(',', $user['boards']);
mod_page(_('Edit user'), 'mod/user.html', array('user' => $user, 'logs' => $log, 'boards' => listBoards()));
mod_page(_('Edit user'), 'mod/user.html', array(
'user' => $user,
'logs' => $log,
'boards' => listBoards(),
'token' => make_secure_link_token('users/' . $user['id'])
));
}
function mod_user_new() {
@ -1728,7 +1827,7 @@ function mod_user_new() {
return;
}
mod_page(_('Edit user'), 'mod/user.html', array('new' => true, 'boards' => listBoards()));
mod_page(_('New user'), 'mod/user.html', array('new' => true, 'boards' => listBoards(), 'token' => make_secure_link_token('users/new')));
}
@ -1738,9 +1837,18 @@ function mod_users() {
if (!hasPermission($config['mod']['manageusers']))
error($config['error']['noaccess']);
$query = query("SELECT *, (SELECT `time` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `last`, (SELECT `text` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `action` FROM ``mods`` ORDER BY `type` DESC,`id`") or error(db_error());
$query = query("SELECT
*,
(SELECT `time` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `last`,
(SELECT `text` FROM ``modlogs`` WHERE `mod` = `id` ORDER BY `time` DESC LIMIT 1) AS `action`
FROM ``mods`` ORDER BY `type` DESC,`id`") or error(db_error());
$users = $query->fetchAll(PDO::FETCH_ASSOC);
foreach ($users as &$user) {
$user['promote_token'] = make_secure_link_token("users/{$user['id']}/promote");
$user['demote_token'] = make_secure_link_token("users/{$user['id']}/demote");
}
mod_page(sprintf('%s (%d)', _('Manage users'), count($users)), 'mod/users.html', array('users' => $users));
}
@ -1832,7 +1940,10 @@ function mod_pm($id, $reply = false) {
error($config['error']['404']); // deleted?
mod_page(sprintf('%s %s', _('New PM for'), $pm['to_username']), 'mod/new_pm.html', array(
'username' => $pm['username'], 'id' => $pm['sender'], 'message' => quote($pm['message'])
'username' => $pm['username'],
'id' => $pm['sender'],
'message' => quote($pm['message']),
'token' => make_secure_link_token('new_PM/' . $pm['username'])
));
} else {
mod_page(sprintf('%s &ndash; #%d', _('Private message'), $id), 'mod/pm.html', $pm);
@ -1904,7 +2015,11 @@ function mod_new_pm($username) {
header('Location: ?/', true, $config['redirect_http']);
}
mod_page(sprintf('%s %s', _('New PM for'), $username), 'mod/new_pm.html', array('username' => $username, 'id' => $id));
mod_page(sprintf('%s %s', _('New PM for'), $username), 'mod/new_pm.html', array(
'username' => $username,
'id' => $id,
'token' => make_secure_link_token('new_PM/' . $username)
));
}
function mod_rebuild() {
@ -1973,7 +2088,10 @@ function mod_rebuild() {
return;
}
mod_page(_('Rebuild'), 'mod/rebuild.html', array('boards' => listBoards()));
mod_page(_('Rebuild'), 'mod/rebuild.html', array(
'boards' => listBoards(),
'token' => make_secure_link_token('rebuild')
));
}
function mod_reports() {
@ -2028,7 +2146,13 @@ function mod_reports() {
}
// a little messy and inefficient
$append_html = Element('mod/report.html', array('report' => $report, 'config' => $config, 'mod' => $mod));
$append_html = Element('mod/report.html', array(
'report' => $report,
'config' => $config,
'mod' => $mod,
'token' => make_secure_link_token('reports/' . $report['id'] . '/dismiss'),
'token_all' => make_secure_link_token('reports/' . $report['id'] . '/dismissall')
));
// Bug fix for https://github.com/savetheinternet/Tinyboard/issues/21
$po->body = truncate($po->body, $po->link(), $config['body_truncate'] - substr_count($append_html, '<br>'));
@ -2131,7 +2255,8 @@ function mod_config($board_config = false) {
'readonly' => $readonly,
'boards' => listBoards(),
'board' => $board_config,
'file' => $config_file
'file' => $config_file,
'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : ''))
));
return;
}
@ -2214,17 +2339,18 @@ function mod_config($board_config = false) {
}
}
header('Location: ?/config', true, $config['redirect_http']);
header('Location: ?/config' . ($board_config ? '/' . $board_config : ''), true, $config['redirect_http']);
exit;
}
mod_page(_('Config editor') . ($board_config ? ': ' . sprintf($config['board_abbreviation'], $board_config) : ''),
'mod/config-editor.html', array(
'boards' => listBoards(),
'board' => $board_config,
'conf' => $conf,
'file' => $config_file
'file' => $config_file,
'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : ''))
));
}
@ -2250,6 +2376,11 @@ function mod_themes_list() {
}
}
closedir($dir);
foreach ($themes as $theme_name => &$theme) {
$theme['rebuild_token'] = make_secure_link_token('themes/' . $theme_name . '/rebuild');
$theme['uninstall_token'] = make_secure_link_token('themes/' . $theme_name . '/uninstall');
}
mod_page(_('Manage themes'), 'mod/themes.html', array(
'themes' => $themes,
@ -2320,7 +2451,7 @@ function mod_theme_configure($theme_name) {
'theme_name' => $theme_name,
'theme' => $theme,
'result' => $result,
'message' => $message,
'message' => $message
));
return;
}
@ -2331,6 +2462,7 @@ function mod_theme_configure($theme_name) {
'theme_name' => $theme_name,
'theme' => $theme,
'settings' => $settings,
'token' => make_secure_link_token('themes/' . $theme_name)
));
}
@ -2455,3 +2587,24 @@ function mod_debug_sql() {
mod_page(_('Debug: SQL'), 'mod/debug/sql.html', $args);
}
function mod_debug_apc() {
global $config;
if (!hasPermission($config['mod']['debug_apc']))
error($config['error']['noaccess']);
if ($config['cache']['enabled'] != 'apc')
error('APC is not enabled.');
$cache_info = apc_cache_info('user');
// $cached_vars = new APCIterator('user', '/^' . $config['cache']['prefix'] . '/');
$cached_vars = array();
foreach ($cache_info['cache_list'] as $var) {
if ($config['cache']['prefix'] != '' && strpos(isset($var['key']) ? $var['key'] : $var['info'], $config['cache']['prefix']) !== 0)
continue;
$cached_vars[] = $var;
}
mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars));
}

19
install.php

@ -1,7 +1,7 @@
<?php
// Installation/upgrade file
define('VERSION', 'v0.9.6-dev-21 + <a href="https://int.vichan.net/devel/">vichan-devel-4.4.90</a>');
define('VERSION', 'v0.9.6-dev-22 + <a href="https://int.vichan.net/devel/">vichan-devel-4.4.91</a>');
require 'inc/functions.php';
@ -13,7 +13,7 @@ $page = array(
'nojavascript' => true
);
// this breaks the dispaly of licenses if enabled
// this breaks the display of licenses if enabled
$config['minify_html'] = false;
if (file_exists($config['has_installed'])) {
@ -428,7 +428,7 @@ if (file_exists($config['has_installed'])) {
query("UPDATE ``mods`` SET `type` = 30 WHERE `type` = 2") or error(db_error());
query("ALTER TABLE ``mods`` CHANGE `type` `type` smallint(1) NOT NULL") or error(db_error());
case 'v0.9.6-dev-20':
query("CREATE TABLE IF NOT EXISTS `bans_new_temp` (
__query("CREATE TABLE IF NOT EXISTS `bans_new_temp` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ipstart` varbinary(16) NOT NULL,
`ipend` varbinary(16) DEFAULT NULL,
@ -487,7 +487,18 @@ if (file_exists($config['has_installed'])) {
query("DROP TABLE ``bans``") or error(db_error());
// Replace with new table
query("RENAME TABLE ``bans_new_temp`` TO ``bans``") or error(db_error());
case 'v0.9.6-dev-21':
case 'v0.9.6-dev-21':
case 'v0.9.6-dev-21 + <a href="https://int.vichan.net/devel/">vichan-devel-4.4.90</a>':
__query("CREATE TABLE IF NOT EXISTS ``ban_appeals`` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ban_id` int(10) unsigned NOT NULL,
`time` int(10) unsigned NOT NULL,
`message` text NOT NULL,
`denied` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `ban_id` (`ban_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;") or error(db_error());
case 'v0.9.6-dev-22':
case false:
// Update version number
file_write($config['has_installed'], VERSION);

16
install.sql

@ -280,6 +280,22 @@ CREATE TABLE IF NOT EXISTS `flood` (
KEY `time` (`time`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii COLLATE=ascii_bin AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
--
-- Table structure for table `ban_appeals`
--
CREATE TABLE IF NOT EXISTS `ban_appeals` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ban_id` int(10) unsigned NOT NULL,
`time` int(10) unsigned NOT NULL,
`message` text NOT NULL,
`denied` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `ban_id` (`ban_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
/*!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 */;

12
js/quick-reply.js

@ -370,13 +370,19 @@
if ($(this).width() <= 800)
return;
show_quick_reply();
$('#quick-reply textarea').focus();
if (with_link) {
$(window).ready(function() {
$(document).ready(function() {
if ($('#' + id).length) {
highlightReply(id);
$(window).scrollTop($('#' + id).offset().top);
$(document).scrollTop($('#' + id).offset().top);
}
// Honestly, I'm not sure why we need setTimeout() here, but it seems to work.
// Same for the "tmp" variable stuff you see inside here:
setTimeout(function() {
var tmp = $('#quick-reply textarea[name="body"]').val();
$('#quick-reply textarea[name="body"]').val('').focus().val(tmp);
}, 1);
});
}
});

86
mod.php

@ -24,48 +24,51 @@ if (get_magic_quotes_gpc()) {
$query = isset($_SERVER['QUERY_STRING']) ? rawurldecode($_SERVER['QUERY_STRING']) : '';
$pages = array(
'' => ':?/', // re