Conflicts: inc/config.php install.php post.php stylesheets/style.csstags/vichan-devel-4.4.92
@@ -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 | |||
------------ | |||
@@ -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(); | |||
@@ -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; | |||
@@ -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}"]; | |||
@@ -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); | |||
@@ -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); | |||
@@ -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']); | |||
} | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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; | |||
@@ -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); | |||
@@ -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; | |||
} | |||
/** | |||
@@ -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); | |||
} | |||
@@ -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; | |||
} | |||
} | |||
@@ -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; | |||
@@ -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')); | |||
} | |||
@@ -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(')') | |||
; | |||
} | |||
} |
@@ -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')); | |||
@@ -235,9 +235,41 @@ 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") | |||
; | |||
} | |||
@@ -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 | |||
@@ -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() | |||
@@ -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; | |||
@@ -446,6 +448,66 @@ abstract class Twig_Template implements Twig_TemplateInterface | |||
} | |||
/** | |||
* 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. | |||
*/ | |||
public static function clearCache() | |||
@@ -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; | |||
@@ -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 – #%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)); | |||
} |
@@ -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); | |||
@@ -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 */; |
@@ -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); | |||
}); | |||
} | |||
}); | |||
@@ -24,48 +24,51 @@ if (get_magic_quotes_gpc()) { | |||
$query = isset($_SERVER['QUERY_STRING']) ? rawurldecode($_SERVER['QUERY_STRING']) : ''; | |||
$pages = array( | |||
'' => ':?/', // redirect to dashboard | |||
'/' => 'dashboard', // dashboard | |||
'/confirm/(.+)' => 'confirm', // confirm action (if javascript didn't work) | |||
'/logout' => 'logout', // logout | |||
'' => ':?/', // redirect to dashboard | |||
'/' => 'dashboard', // dashboard | |||
'/confirm/(.+)' => 'confirm', // confirm action (if javascript didn't work) | |||
'/logout' => 'secure logout', // logout | |||
'/users' => 'users', // manage users | |||
'/users/(\d+)' => 'user', // edit user | |||
'/users/(\d+)/(promote|demote)' => 'user_promote', // prmote/demote user | |||
'/users/new' => 'user_new', // create a new user | |||
'/new_PM/([^/]+)' => 'new_pm', // create a new pm | |||
'/PM/(\d+)(/reply)?' => 'pm', // read a pm | |||
'/inbox' => 'inbox', // pm inbox | |||
'/users' => 'users', // manage users | |||
'/users/(\d+)/(promote|demote)' => 'secure user_promote', // prmote/demote user | |||
'/users/(\d+)' => 'secure_POST user', // edit user | |||
'/users/new' => 'secure_POST user_new', // create a new user | |||
'/noticeboard' => 'noticeboard', // view noticeboard | |||
'/noticeboard/(\d+)' => 'noticeboard', // view noticeboard | |||
'/noticeboard/delete/(\d+)' => 'noticeboard_delete', // delete from noticeboard | |||
'/log' => 'log', // modlog | |||
'/log/(\d+)' => 'log', // modlog | |||
'/log:([^/]+)' => 'user_log', // modlog | |||
'/log:([^/]+)/(\d+)' => 'user_log', // modlog | |||
'/news' => 'news', // view news | |||
'/news/(\d+)' => 'news', // view news | |||
'/news/delete/(\d+)' => 'news_delete', // delete from news | |||
'/new_PM/([^/]+)' => 'secure_POST new_pm', // create a new pm | |||
'/PM/(\d+)(/reply)?' => 'pm', // read a pm | |||
'/inbox' => 'inbox', // pm inbox | |||
'/edit/(\%b)' => 'edit_board', // edit board details | |||
'/new-board' => 'new_board', // create a new board | |||
'/log' => '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 | |||
'/rebuild' => 'rebuild', // rebuild static files | |||
'/reports' => 'reports', // report queue | |||
'/reports/(\d+)/dismiss(all)?' => 'report_dismiss', // dismiss a report | |||
'/noticeboard' => 'secure_POST noticeboard', // view noticeboard | |||
'/noticeboard/(\d+)' => 'secure_POST noticeboard', // view noticeboard | |||
'/noticeboard/delete/(\d+)' => 'secure noticeboard_delete', // delete from noticeboard | |||
'/IP/([\w.:]+)' => 'ip', // view ip address | |||
'/IP/([\w.:]+)/remove_note/(\d+)' => 'ip_remove_note', // remove note from ip address | |||
'/bans' => 'bans', // ban list | |||
'/bans/(\d+)' => 'bans', // ban list | |||
'/edit/(\%b)' => 'secure_POST edit_board', // edit board details | |||
'/new-board' => 'secure_POST new_board', // create a new board | |||
'/rebuild' => 'secure_POST rebuild', // rebuild static files | |||
'/reports' => 'reports', // report queue | |||
'/reports/(\d+)/dismiss(all)?' => 'secure report_dismiss', // dismiss a report | |||
'/IP/([\w.:]+)' => 'secure_POST ip', // view ip address | |||
'/IP/([\w.:]+)/remove_note/(\d+)' => 'secure ip_remove_note', // remove note from ip address | |||
'/search' => 'search_redirect', // search | |||
'/search/(posts|IP_notes|bans|log)/(.+)/(\d+)' => 'search', // search | |||
'/search/(posts|IP_notes|bans|log)/(.+)' => 'search', // search | |||
// CSRF-protected moderator actions | |||
'/ban' => 'secure_POST ban', // new ban | |||
'/bans' => 'secure_POST bans', // ban list | |||
'/bans/(\d+)' => 'secure_POST bans', // ban list | |||
'/ban-appeals' => 'secure_POST ban_appeals', // view ban appeals | |||
'/search' => 'search_redirect', // search | |||
'/search/(posts|IP_notes|bans|log)/(.+)/(\d+)' => 'search', // search | |||
'/search/(posts|IP_notes|bans|log)/(.+)' => 'search', // search | |||
'/(\%b)/ban(&delete)?/(\d+)' => 'secure_POST ban_post', // ban poster | |||
'/(\%b)/move/(\d+)' => 'secure_POST move', // move thread | |||
'/(\%b)/move_reply/(\d+)' => 'secure_POST move_reply', // move reply | |||
@@ -78,17 +81,18 @@ $pages = array( | |||
'/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread | |||
'/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread | |||
'/themes' => 'themes_list', // manage themes | |||
'/themes/(\w+)' => 'theme_configure', // configure/reconfigure theme | |||
'/themes/(\w+)/rebuild' => 'theme_rebuild', // rebuild theme | |||
'/themes/(\w+)/uninstall' => 'theme_uninstall', // uninstall theme | |||
'/themes' => 'themes_list', // manage themes | |||
'/themes/(\w+)' => 'secure_POST theme_configure', // configure/reconfigure theme | |||
'/themes/(\w+)/rebuild' => 'secure theme_rebuild', // rebuild theme | |||
'/themes/(\w+)/uninstall' => 'secure theme_uninstall', // uninstall theme | |||
'/config' => 'config', // config editor | |||
'/config/(\%b)' => 'config', // config editor | |||
'/config' => 'secure_POST config', // config editor | |||
'/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', | |||
// This should always be at the end: | |||
@@ -181,7 +181,8 @@ if (isset($_POST['delete'])) { | |||
error($config['error']['bot']); | |||
// Check the referrer | |||
if (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER']))) | |||
if ($config['referer_match'] !== false && | |||
(!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER'])))) | |||
error($config['error']['referer']); | |||
checkDNSBL(); | |||
@@ -779,6 +780,47 @@ if (isset($_POST['delete'])) { | |||
'id' => $id | |||
)); | |||
} | |||
} elseif (isset($_POST['appeal'])) { | |||
if (!isset($_POST['ban_id'])) | |||
error($config['error']['bot']); | |||
$ban_id = (int)$_POST['ban_id']; | |||
$bans = Bans::find($_SERVER['REMOTE_ADDR']); | |||
foreach ($bans as $_ban) { | |||
if ($_ban['id'] == $ban_id) { | |||
$ban = $_ban; | |||
break; | |||
} | |||
} | |||
if (!isset($ban)) { | |||
error(_("That ban doesn't exist or is not for you.")); | |||
} | |||
if ($ban['expires'] && $ban['expires'] - $ban['created'] <= $config['ban_appeals_min_length']) { | |||
error(_("You cannot appeal a ban of this length.")); | |||
} | |||
$query = query("SELECT `denied` FROM ``ban_appeals`` WHERE `ban_id` = $ban_id") or error(db_error()); | |||
$ban_appeals = $query->fetchAll(PDO::FETCH_COLUMN); | |||
if (count($ban_appeals) >= $config['ban_appeals_max']) { | |||
error(_("You cannot appeal this ban again.")); | |||
} | |||
foreach ($ban_appeals as $is_denied) { | |||
if (!$is_denied) | |||
error(_("There is already a pending appeal for this ban.")); | |||
} | |||
$query = prepare("INSERT INTO ``ban_appeals`` VALUES (NULL, :ban_id, :time, :message, 0)"); | |||
$query->bindValue(':ban_id', $ban_id, PDO::PARAM_INT); | |||
$query->bindValue(':time', time(), PDO::PARAM_INT); | |||
$query->bindValue(':message', $_POST['appeal']); | |||
$query->execute() or error(db_error($query)); | |||
displayBan($ban); | |||
} else { | |||
if (!file_exists($config['has_installed'])) { | |||
header('Location: install.php', true, $config['redirect_http']); | |||
@@ -478,3 +478,10 @@ p.intro.thread-hidden { | |||
margin: 0px; | |||
padding: 0px; | |||
} | |||
form.ban-appeal { | |||
margin: 9px 20px; | |||
} | |||
form.ban-appeal textarea { | |||
display: block; | |||
} |
@@ -77,16 +77,60 @@ | |||
</p> | |||
<p>{% trans %}Your IP address is{% endtrans %} <strong>{{ ban.ip }}</strong>.</p> | |||
{% if post %} | |||
{% if config.ban_page_extra %} | |||
<p>{{ config.ban_page_extra }}</p> | |||
{% endif %} | |||
{% if post and config.ban_show_post %} | |||
<hr> | |||
<p>You were banned for the following post on {{ board.url }}:</p> | |||
<p>{% trans %}You were banned for the following post on {% endtrans %}{{ board.url }}:</p> | |||
{{ post }} | |||
<br> | |||
{% endif %} | |||
{% if config.ban_page_extra %} | |||
<p>{{ config.ban_page_extra }}</p> | |||
{% if config.ban_appeals and (not ban.expires or ban.expires - ban.created > config.ban_appeals_min_length )%} | |||
<hr> | |||
{% if pending_appeal %} | |||
<p> | |||
{% trans %}You submitted an appeal for this ban on{% endtrans %} | |||
<strong>{{ pending_appeal|date(config.ban_date) }}</strong>. {% trans %}It is still pending{% endtrans %}. | |||
</p> | |||
{% elseif denied_appeals|length >= config.ban_appeals_max %} | |||
{% if denied_appeals|length == 1 %} | |||
<p> | |||
{% trans %}You appealed this ban on{% endtrans %} | |||
<strong>{{ denied_appeals[0]|date(config.ban_date) }}</strong> | |||
{% trans %}and it was denied. You may not appeal this ban again.{% endtrans %} | |||
</p> | |||
{% else %} | |||
<p>{% trans %}You have submitted the maximum number of ban appeals allowed. You may not appeal this ban again.{% endtrans %}</p> | |||
{% endif %} | |||
{% else %} | |||
{% if denied_appeals|length %} | |||
{% if denied_appeals|length == 1 %} | |||
<p> | |||
{% trans %}You appealed this ban on{% endtrans %} | |||
<strong>{{ denied_appeals[0]|date(config.ban_date) }}</strong> | |||
{% trans %}and it was denied.{% endtrans %} | |||
</p> | |||
<p>{% trans %}You may appeal this ban again. Please enter your reasoning below.{% endtrans %}</p> | |||
{% else %} | |||
<p> | |||
{% trans %}You last appealed this ban on{% endtrans %} | |||
<strong>{{ denied_appeals[denied_appeals|length - 1]|date(config.ban_date) }}</strong> | |||
{% trans %}and it was denied.{% endtrans %} | |||
</p> | |||
<p>{% trans %}You may appeal this ban again. Please enter your reasoning below.{% endtrans %}</p> | |||
{% endif %} | |||
{% else %} | |||
<p>{% trans %}You may appeal this ban. Please enter your reasoning below.{% endtrans %}</p> | |||
{% endif %} | |||
<form class="ban-appeal" action="" method="post"> | |||
<input type="hidden" name="ban_id" value="{{ ban.id }}"> | |||
<textarea name="appeal" rows="4" cols="40"></textarea> | |||
<input type="submit" value="Submit"> | |||
</form> | |||
{% endif %} | |||
{% endif %} | |||
</div> | |||
{% endfilter %} | |||
{% endfilter %} |
@@ -0,0 +1,107 @@ | |||
{% for ban in ban_appeals %} | |||
<form action="" method="post" style="margin: 10px 0"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table style="margin: 5px 0"> | |||
<tr> | |||
<th>{% trans 'Status' %}</th> | |||
<td> | |||
{% if config.mod.view_banexpired and ban.expires != 0 and ban.expires < time() %} | |||
{% trans 'Expired' %} | |||
{% else %} | |||
{% trans 'Active' %} | |||
{% endif %} | |||
</td> | |||
</tr> | |||
{% if mod|hasPermission(config.mod.show_ip, board.uri) %} | |||
<tr> | |||
<th>{% trans 'IP' %}</th> | |||
<td>{{ ban.mask }}</td> | |||
</tr> | |||
{% endif %} | |||
<tr> | |||
<th>{% trans 'Reason' %}</th> | |||
<td> | |||
{% if ban.reason %} | |||
{{ ban.reason }} | |||
{% else %} | |||
<em>{% trans 'no reason' %}</em> | |||
{% endif %} | |||
</td> | |||
</tr> | |||
<tr> | |||
<th>{% trans 'Board' %}</th> | |||
<td> | |||
{% if ban.board %} | |||
{{ config.board_abbreviation|sprintf(ban.board) }} | |||
{% else %} | |||
<em>{% trans 'all boards' %}</em> | |||
{% endif %} | |||
</td> | |||
</tr> | |||
<tr> | |||
<th>{% trans 'Set' %}</th> | |||
<td>{{ ban.created|date(config.post_date) }}</td> | |||
</tr> | |||
<tr> | |||
<th>{% trans 'Expires' %}</th> | |||
<td> | |||
{% if ban.expires %} | |||
{{ ban.expires|date(config.post_date) }} | |||
{% else %} | |||
<em>{% trans 'never' %}</em> | |||
{% endif %} | |||
</td> | |||
</tr> | |||
<tr> | |||
<th>{% trans 'Seen' %}</th> | |||
<td> | |||
{% if ban.seen %} | |||
{% trans 'Yes' %} | |||
{% else %} | |||
{% trans 'No' %} | |||
{% endif %} | |||
</td> | |||
</tr> | |||
<tr> | |||
<th>{% trans 'Staff' %}</th> | |||
<td> | |||
{% if ban.username %} | |||
{{ ban.username|e }} | |||
{% else %} | |||
<em>{% trans 'deleted?' %}</em> | |||
{% endif %} | |||
</td> | |||
</tr> | |||
</table> | |||
<table style="margin: 10px 0"> | |||
<tr> | |||
<th>{% trans 'Appeal time' %}</th> | |||
<td>{{ ban.time|date(config.post_date) }}</td> | |||
</tr> | |||
<tr> | |||
<th>{% trans 'Appeal reason' %}</th> | |||
<td>{{ ban.message|e }}</td> | |||
</tr> | |||
{% if mod|hasPermission(config.mod.ban_appeals, board.uri) %} | |||
<tr> | |||
<th>{% trans 'Action' %}</th> | |||
<td> | |||
<input type="hidden" name="appeal_id" value="{{ ban.id }}"> | |||
<input type="submit" name="unban" value="Unban"> | |||
<input type="submit" name="deny" value="Deny appeal"> | |||
</td> | |||
</tr> | |||
{% endif %} | |||
</table> | |||
{% if ban.post %} | |||
<div style=""> | |||
{{ ban.post.build(true) }} | |||
</div> | |||
{% endif %} | |||
</form> | |||
<hr> | |||
{% endfor %} |
@@ -1,7 +1,8 @@ | |||
{% if bans|count == 0 %} | |||
<p style="text-align:center" class="unimportant">({% trans 'There are no active bans.' %})</p> | |||
{% else %} | |||
<form action="" method="post"> | |||
<form action="?/bans" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table class="mod" style="width:100%"> | |||
<tr> | |||
<th>{% trans 'IP address/mask' %}</th> | |||
@@ -5,6 +5,7 @@ | |||
{% endif %} | |||
<form action="{{ action }}" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table> | |||
<tr> | |||
<th>{% trans 'URI' %}</th> | |||
@@ -21,6 +21,7 @@ | |||
{% if not readonly %}<form method="post" action="">{% endif %} | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<textarea name="code" id="code" style="margin:auto;width:100%;height:500px{% if readonly %};background:#eee" readonly{% else %}"{% endif %}> | |||
{{ php }} | |||
</textarea> | |||
@@ -14,6 +14,7 @@ | |||
</ul> | |||
{% endif %} | |||
<form method="post" action=""> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table class="mod config-editor"> | |||
<tr> | |||
<th class="minimal">{% trans 'Name' %}</th> | |||
@@ -86,6 +86,9 @@ | |||
{% if mod|hasPermission(config.mod.view_banlist) %} | |||
<li><a href="?/bans">{% trans 'Ban list' %}</a></li> | |||
{% endif %} | |||
{% if config.ban_appeals and mod|hasPermission(config.mod.view_ban_appeals) %} | |||
<li><a href="?/ban-appeals">{% trans 'Ban appeals' %}</a></li> | |||
{% endif %} | |||
{% if mod|hasPermission(config.mod.manageusers) %} | |||
<li><a href="?/users">{% trans 'Manage users' %}</a></li> | |||
{% elseif mod|hasPermission(config.mod.change_password) %} | |||
@@ -161,7 +164,7 @@ | |||
<legend>{% trans 'User account' %}</legend> | |||
<ul> | |||
<li><a href="?/logout">{% trans 'Logout' %}</a></li> | |||
<li><a href="?/logout/{{ logout_token }}">{% trans 'Logout' %}</a></li> | |||
</ul> | |||
</fieldset> | |||
@@ -0,0 +1,18 @@ | |||
<table class="modlog"> | |||
<tr> | |||
<th class="minimal">Key</th> | |||
<th class="minimal">Hits</th> | |||
<th class="minimal">Created</th> | |||
<th class="minimal">Expires</th> | |||
<th class="minimal">Size</th> | |||
</tr> | |||
{% for var in cached_vars if (var.ctime is defined ? var.ctime : var.creation_time) + var.ttl > time() %} | |||
<tr> | |||
<td class="minimal">{{ var.key is defined ? var.key : var.info }}</td> | |||
<td class="minimal">{{ var.nhits is defined ? var.nhits : var.num_hits }}</td> | |||
<td class="minimal">{{ (var.ctime is defined ? var.ctime : var.creation_time)|ago }} ago</td> | |||
<td class="minimal">{{ ((var.ctime is defined ? var.ctime : var.creation_time) + var.ttl)|until }} (ttl: {{ (time() + var.ttl)|until }})</td> | |||
<td class="minimal">{{ var.mem_size }} bytes</td> | |||
</tr> | |||
{% endfor %} | |||
</table> |
@@ -1,4 +1,5 @@ | |||
<form action="?/new_PM/{{ username|e }}" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table> | |||
<tr> | |||
<th>To</th> | |||
@@ -2,6 +2,7 @@ | |||
<fieldset> | |||
<legend>{% trans 'New post' %}</legend> | |||
<form style="margin:0" action="" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table> | |||
<tr> | |||
<th> | |||
@@ -39,7 +40,7 @@ | |||
<div class="ban"> | |||
{% if mod|hasPermission(config.mod.news_delete) %} | |||
<span style="float:right;padding:2px"> | |||
<a class="unimportant" href="?/news/delete/{{ post.id }}">[{% trans 'delete' %}]</a> | |||
<a class="unimportant" href="?/news/delete/{{ post.id }}/{{ post.delete_token }}">[{% trans 'delete' %}]</a> | |||
</span> | |||
{% endif %} | |||
<h2 id="{{ post.id }}"> | |||
@@ -1,7 +1,8 @@ | |||
{% if mod|hasPermission(config.mod.noticeboard_post) %} | |||
<fieldset> | |||
<legend>{% trans 'New post' %}</legend> | |||
<form style="margin:0" action="" method="post"> | |||
<form style="margin:0" action="?/noticeboard" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table> | |||
<tr> | |||
<th>{% trans 'Name' %}</th> | |||
@@ -27,7 +28,7 @@ | |||
<div class="ban"> | |||
{% if mod|hasPermission(config.mod.noticeboard_delete) %} | |||
<span style="float:right;padding:2px"> | |||
<a class="unimportant" href="?/noticeboard/delete/{{ post.id }}">[{% trans 'delete' %}]</a> | |||
<a class="unimportant" href="?/noticeboard/delete/{{ post.id }}/{{ post.delete_token }}">[{% trans 'delete' %}]</a> | |||
</span> | |||
{% endif %} | |||
<h2 id="{{ post.id }}"> | |||
@@ -1,4 +1,5 @@ | |||
<form style="width:300px;margin:auto" action="?/rebuild" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<ul id="rebuild"> | |||
<li style="margin-bottom:8px"> | |||
<input type="checkbox" name="rebuild_all" id="rebuild_all" onchange="toggleall(this.checked)"> | |||
@@ -13,13 +13,13 @@ | |||
{% if mod|hasPermission(config.mod.report_dismiss, report.board) or mod|hasPermission(config.mod.report_dismiss_ip, report.board) %} | |||
<hr> | |||
{% if mod|hasPermission(config.mod.report_dismiss, report.board) %} | |||
<a title="{% trans 'Discard abuse report' %}" href="?/reports/{{ report.id }}/dismiss">Dismiss</a> | |||
<a title="{% trans 'Discard abuse report' %}" href="?/reports/{{ report.id }}/dismiss/{{ token }}">Dismiss</a> | |||
{% endif %} | |||
{% if mod|hasPermission(config.mod.report_dismiss_ip, report.board) %} | |||
{% if mod|hasPermission(config.mod.report_dismiss, report.board) %} | |||
| | |||
{% endif %} | |||
<a title="{% trans 'Discard all abuse reports by this IP address' %}" href="?/reports/{{ report.id }}/dismissall">Dismiss+</a> | |||
<a title="{% trans 'Discard all abuse reports by this IP address' %}" href="?/reports/{{ report.id }}/dismissall/{{ token_all }}">Dismiss+</a> | |||
{% endif %} | |||
{% endif %} | |||
</div> | |||
@@ -1,4 +1,5 @@ | |||
<form action="" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
{% if not config %} | |||
<p style="text-align:center" class="unimportant">(No configuration required.)</p> | |||
{% else %} | |||
@@ -28,8 +28,8 @@ | |||
{% if theme_name in themes_in_use %}{% trans 'Reconfigure' %}{% else %}{% trans 'Install' %}{% endif %} | |||
</a></li> | |||
{% if theme_name in themes_in_use %} | |||
<li><a href="?/themes/{{ theme_name }}/rebuild">{% trans 'Rebuild' %}</a></li> | |||
<li><a href="?/themes/{{ theme_name }}/uninstall" onclick="return confirm('Are you sure you want to uninstall this theme?');">{% trans 'Uninstall' %}</a></li> | |||
<li><a href="?/themes/{{ theme_name }}/rebuild/{{ theme.rebuild_token }}">{% trans 'Rebuild' %}</a></li> | |||
<li><a href="?/themes/{{ theme_name }}/uninstall/{{ theme.uninstall_token }}" onclick="return confirm('Are you sure you want to uninstall this theme?');">{% trans 'Uninstall' %}</a></li> | |||
{% endif %} | |||
</ul></td> | |||
</tr> | |||
@@ -5,6 +5,7 @@ | |||
{% endif %} | |||
<form action="{{ action }}" method="post"> | |||
<input type="hidden" name="token" value="{{ token }}"> | |||
<table> | |||
<tr> | |||
<th>{% trans 'Username' %}</th> | |||
@@ -48,10 +48,10 @@ | |||
{% endif %} | |||
<td> | |||
{% if mod|hasPermission(config.mod.promoteusers) and user.type < constant(config.mod.groups[0:-1]|last) %} | |||
<a style="float:left;text-decoration:none" href="?/users/{{ user.id }}/promote" title="{% trans 'Promote' %}">▲</a> | |||
<a style="float:left;text-decoration:none" href="?/users/{{ user.id }}/promote/{{ user.promote_token }}" title="{% trans 'Promote' %}">▲</a> | |||
{% endif %} | |||
{% if mod|hasPermission(config.mod.promoteusers) and user.type > constant(config.mod.groups|first) %} | |||
<a style="float:left;text-decoration:none" href="?/users/{{ user.id }}/demote" title="{% trans 'Demote' %}"{% if mod.id == user.id %} onclick="return confirm('{% trans 'Are you sure you want to demote yourself?' %}')"{% endif %}>▼</a> | |||
<a style="float:left;text-decoration:none" href="?/users/{{ user.id }}/demote/{{ user.demote_token }}" title="{% trans 'Demote' %}"{% if mod.id == user.id %} onclick="return confirm('{% trans 'Are you sure you want to demote yourself?' %}')"{% endif %}>▼</a> | |||
{% endif %} | |||
{% if mod|hasPermission(config.mod.modlog) %} | |||
<a class="unimportant" style="margin-left:5px;float:right" href="?/log:{{ user.username|e }}">[{% trans 'log' %}]</a> | |||
@@ -57,6 +57,7 @@ | |||
{% if mod|hasPermission(config.mod.create_notes) %} | |||
<form action="" method="post" style="margin:0"> | |||
<input type="hidden" name="token" value="{{ security_token }}"> | |||
<table> | |||
<tr> | |||
<th>{% trans 'Staff' %}</th> | |||
@@ -87,6 +88,7 @@ | |||
{% for ban in bans %} | |||
<form action="" method="post" style="text-align:center"> | |||
<input type="hidden" name="token" value="{{ security_token }}"> | |||
<table style="width:400px;margin-bottom:10px;border-bottom:1px solid #ddd;padding:5px"> | |||
<tr> | |||
<th>{% trans 'Status' %}</th> | |||