Compare commits
63 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e88121236c | ||
![]() |
13598f350e | ||
![]() |
46714487d4 | ||
![]() |
7530f5fee0 | ||
![]() |
8ec4676535 | ||
![]() |
6917fc8e53 | ||
![]() |
f53b6965dc | ||
![]() |
6687175940 | ||
![]() |
46df93ef9c | ||
![]() |
d091b7e30d | ||
![]() |
db591e53fa | ||
![]() |
766e79b8ed | ||
![]() |
af6fd98f09 | ||
![]() |
f3976236ec | ||
![]() |
ef8e34c861 | ||
![]() |
a5c27acb2a | ||
![]() |
70e196eefd | ||
![]() |
fe532b6990 | ||
![]() |
85e2c3ed29 | ||
![]() |
3299b641a7 | ||
![]() |
b7001bc83c | ||
![]() |
baaecfcea7 | ||
![]() |
164e0f8653 | ||
![]() |
56160a3a94 | ||
![]() |
e98a014f24 | ||
![]() |
5dc6ecfa23 | ||
![]() |
4bd6f93161 | ||
![]() |
568e119a79 | ||
![]() |
06486e890b | ||
![]() |
3d6501ac7c | ||
![]() |
b39197b70e | ||
![]() |
26cd5d3c17 | ||
![]() |
47882fb1d1 | ||
![]() |
c2be23603a | ||
![]() |
fadf4098ba | ||
![]() |
1ac5847399 | ||
![]() |
b3d238c1cd | ||
![]() |
7ef1e68af7 | ||
![]() |
d10d8aa2c9 | ||
![]() |
032301df17 | ||
![]() |
113df48a3c | ||
![]() |
a0eb7a0e27 | ||
![]() |
1d57ade40f | ||
![]() |
fabc46f8ee | ||
![]() |
2c87b98d24 | ||
![]() |
2feaff7b5c | ||
![]() |
956a2b2d67 | ||
![]() |
5ebf9eb3f6 | ||
![]() |
c648a52651 | ||
![]() |
d4969ae009 | ||
![]() |
40563b4ffc | ||
![]() |
5e1e956de6 | ||
![]() |
7a2efd3bd5 | ||
![]() |
f40602fd82 | ||
![]() |
223efdfb8f | ||
![]() |
ced3c7cd15 | ||
![]() |
4495f64268 | ||
![]() |
0e8e5cd87a | ||
![]() |
2e64177610 | ||
![]() |
432a441a2c | ||
![]() |
5a3ad9d33e | ||
![]() |
2ddd7796b0 | ||
![]() |
9f1d781beb |
60 changed files with 1949 additions and 206 deletions
|
@ -4,6 +4,9 @@
|
|||
#
|
||||
AddDefaultCharset UTF-8
|
||||
|
||||
#Options +FollowSymLinks # For extensions with symlinks
|
||||
##Options -FollowSymLinks +SymLinksIfOwnerMatch # or this (more security(?), more checks(!!!))
|
||||
|
||||
<IfModule mod_autoindex.c>
|
||||
Options -Indexes
|
||||
</IfModule>
|
||||
|
|
25
.gitignore
vendored
25
.gitignore
vendored
|
@ -1,19 +1,32 @@
|
|||
/_*
|
||||
/.htaccess
|
||||
/index.php
|
||||
/app/config/main.php
|
||||
/app/config/_*
|
||||
/app/config/db/*
|
||||
/app/config/ext/*
|
||||
/app/cache/**/*.php
|
||||
/app/cache/**/*.lock
|
||||
/app/cache/**/*.tmp
|
||||
/app/cache/*
|
||||
/app/log/*
|
||||
/ext/*
|
||||
/public/img/avatars/*
|
||||
/public/img/og/*
|
||||
/public/.htaccess
|
||||
/public/index.php
|
||||
/public/img/*
|
||||
/public/style/*
|
||||
/public/upload/**/*
|
||||
/public/style/ForkBB_old/*
|
||||
!/public/img/sm/big_smile.png
|
||||
!/public/img/sm/cool.png
|
||||
!/public/img/sm/hmm.png
|
||||
!/public/img/sm/lol.png
|
||||
!/public/img/sm/mad.png
|
||||
!/public/img/sm/neutral.png
|
||||
!/public/img/sm/roll.png
|
||||
!/public/img/sm/sad.png
|
||||
!/public/img/sm/smile.png
|
||||
!/public/img/sm/tongue.png
|
||||
!/public/img/sm/wink.png
|
||||
!/public/img/sm/yikes.png
|
||||
!/public/style/font/*
|
||||
!/public/style/ForkBB/*
|
||||
!/public/style/sc/*
|
||||
!/public/upload/index.html
|
||||
!.gitkeep
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017-2023 Visman (mio.visman@yandex.ru)
|
||||
Copyright (c) 2017-2023 Visman (mio.visman@yandex.ru, https://github.com/MioVisman)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -46,7 +46,6 @@ class Primary
|
|||
$confChange = [
|
||||
'multiple' => [
|
||||
'CtrlRouting' => \ForkBB\Controllers\Update::class,
|
||||
|
||||
'AdminUpdate' => \ForkBB\Models\Pages\Admin\Update::class,
|
||||
],
|
||||
];
|
||||
|
|
|
@ -29,6 +29,13 @@ class Routing
|
|||
$config = $this->c->config;
|
||||
$r = $this->c->Router;
|
||||
|
||||
$r->add(
|
||||
$r::GET,
|
||||
'/sitemap{id:\d*}.xml',
|
||||
'Sitemap:view',
|
||||
'Sitemap'
|
||||
);
|
||||
|
||||
// регистрация/вход/выход
|
||||
if ($user->isGuest) {
|
||||
// вход
|
||||
|
@ -97,6 +104,18 @@ class Routing
|
|||
}
|
||||
|
||||
// OAuth
|
||||
if (
|
||||
1 === $config->b_oauth_allow
|
||||
|| $user->isAdmin
|
||||
) {
|
||||
$r->add(
|
||||
$r::GET,
|
||||
'/reglog/callback/{name}',
|
||||
'RegLog:callback',
|
||||
'RegLogCallback'
|
||||
);
|
||||
}
|
||||
|
||||
if (1 === $config->b_oauth_allow) {
|
||||
$r->add(
|
||||
$r::PST,
|
||||
|
@ -104,15 +123,6 @@ class Routing
|
|||
'RegLog:redirect',
|
||||
'RegLogRedirect'
|
||||
);
|
||||
|
||||
if ($user->isAdmin) {
|
||||
$r->add(
|
||||
$r::GET,
|
||||
'/reglog/callback/{name}',
|
||||
'RegLog:callback',
|
||||
'RegLogCallback'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// просмотр разрешен
|
||||
|
@ -629,7 +639,6 @@ class Routing
|
|||
'Moderate:action',
|
||||
'Moderate'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// только админ
|
||||
|
|
|
@ -226,8 +226,6 @@ class FileCache implements CacheInterface
|
|||
{
|
||||
if (\function_exists('\\opcache_invalidate')) {
|
||||
\opcache_invalidate($file, true);
|
||||
} elseif (\function_exists('\\apc_delete_file')) {
|
||||
\apc_delete_file($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -172,6 +172,7 @@ class Config
|
|||
switch ($type) {
|
||||
case 'ZERO':
|
||||
$type = 'NEW';
|
||||
|
||||
break;
|
||||
case 'NEW':
|
||||
case '=>':
|
||||
|
@ -180,6 +181,7 @@ class Config
|
|||
$value_before = $other;
|
||||
$other = '';
|
||||
$type = 'VALUE';
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ForkException('Config array cannot be parsed (3)');
|
||||
|
@ -219,6 +221,7 @@ class Config
|
|||
case 'VALUE':
|
||||
case 'VALUE_OR_KEY':
|
||||
$type = 'NEW';
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ForkException('Config array cannot be parsed (6)');
|
||||
|
@ -234,6 +237,7 @@ class Config
|
|||
$value = null;
|
||||
$value_before = '';
|
||||
$type = '=>';
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ForkException('Config array cannot be parsed (7)');
|
||||
|
@ -251,7 +255,8 @@ class Config
|
|||
case 'VALUE_OR_KEY':
|
||||
case 'VALUE':
|
||||
case '=>':
|
||||
$other .= $token;
|
||||
$other .= $token;
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ForkException('Config array cannot be parsed (8)');
|
||||
|
@ -291,9 +296,11 @@ class Config
|
|||
}
|
||||
|
||||
$type = 'VALUE_OR_KEY';
|
||||
|
||||
break;
|
||||
case '=>':
|
||||
$type = 'VALUE';
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ForkException('Config array cannot be parsed (10)');
|
||||
|
@ -311,11 +318,11 @@ class Config
|
|||
protected function isFormat(mixed $data): bool
|
||||
{
|
||||
return \is_array($data)
|
||||
&& \array_key_exists('value', $data)
|
||||
&& \array_key_exists('value_before', $data)
|
||||
&& \array_key_exists('value_after', $data)
|
||||
&& \array_key_exists('key_before', $data)
|
||||
&& \array_key_exists('key_after', $data);
|
||||
&& \array_key_exists('value', $data)
|
||||
&& \array_key_exists('value_before', $data)
|
||||
&& \array_key_exists('value_after', $data)
|
||||
&& \array_key_exists('key_before', $data)
|
||||
&& \array_key_exists('key_after', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -429,6 +436,7 @@ class Config
|
|||
return false;
|
||||
} else {
|
||||
$result = $config[$key];
|
||||
|
||||
unset($config[$key]);
|
||||
|
||||
return $result;
|
||||
|
|
|
@ -226,10 +226,12 @@ class DB
|
|||
case 's':
|
||||
case 'f':
|
||||
$value = [1];
|
||||
|
||||
break;
|
||||
default:
|
||||
$value = [1];
|
||||
$type = 's';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,8 +67,8 @@ class ErrorHandler
|
|||
\set_error_handler([$this, 'errorHandler'], \E_ALL);
|
||||
\set_exception_handler([$this, 'exceptionHandler']);
|
||||
\register_shutdown_function([$this, 'shutdownHandler']);
|
||||
|
||||
\ob_start();
|
||||
|
||||
$this->obLevel = \ob_get_level();
|
||||
}
|
||||
|
||||
|
@ -198,6 +198,7 @@ class ErrorHandler
|
|||
if (isset($error['exception'])) {
|
||||
$context['exception'] = $error['exception'];
|
||||
}
|
||||
|
||||
$context['headers'] = false;
|
||||
|
||||
$this->c->Log->{$method}($this->message($error), $context);
|
||||
|
@ -279,15 +280,19 @@ EOT;
|
|||
switch ($type) {
|
||||
case 'boolean':
|
||||
$type = $arg ? 'true' : 'false';
|
||||
|
||||
break;
|
||||
case 'array':
|
||||
$type .= '(' . \count($arg) . ')';
|
||||
|
||||
break;
|
||||
case 'resource':
|
||||
$type = \get_resource_type($arg);
|
||||
|
||||
break;
|
||||
case 'object':
|
||||
$type .= '{' . \get_class($arg) . '}';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -296,8 +301,8 @@ EOT;
|
|||
}
|
||||
}
|
||||
$line .= ')';
|
||||
$line = $this->e(\str_replace($this->hidePath, '...', $line));
|
||||
|
||||
$line = $this->e(\str_replace($this->hidePath, '...', $line));
|
||||
echo "<li>{$line}</li>";
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ use ForkBB\Core\File;
|
|||
use ForkBB\Core\Image;
|
||||
use ForkBB\Core\Image\DefaultDriver;
|
||||
use ForkBB\Core\Exceptions\FileException;
|
||||
use Transliterator;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
|
@ -55,6 +56,11 @@ class Files
|
|||
*/
|
||||
protected array $tmpFiles = [];
|
||||
|
||||
/**
|
||||
* Для кэширования транслитератора
|
||||
*/
|
||||
protected Transliterator|false|null $transl = null;
|
||||
|
||||
/**
|
||||
* Список mime типов считающихся картинками
|
||||
*/
|
||||
|
@ -971,12 +977,15 @@ class Files
|
|||
*/
|
||||
public function filterName(string $name): string
|
||||
{
|
||||
$name = \transliterator_transliterate(
|
||||
'Any-Latin; Latin-ASCII; Lower()', // "Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; Lower();",
|
||||
$name
|
||||
);
|
||||
if (null === $this->transl) {
|
||||
$this->transl = Transliterator::create('Any-Latin;Latin-ASCII;Lower();') ?? false;
|
||||
}
|
||||
|
||||
$name = \trim(\preg_replace(['%[^\w-]+%', '%_+%'], ['-', '_'], $name), '-_');
|
||||
if ($this->transl instanceof Transliterator) {
|
||||
$name = $this->transl->transliterate($name);
|
||||
}
|
||||
|
||||
$name = \trim(\preg_replace(['%[^\w]+%', '%_+%'], ['-', '_'], $name), '-_');
|
||||
|
||||
return isset($name[0]) ? $name : (string) \time();
|
||||
}
|
||||
|
@ -1262,7 +1271,7 @@ class Files
|
|||
/**
|
||||
* Переменные конфига подключения
|
||||
*/
|
||||
protected int $actMaxRedir = 10;
|
||||
protected int $actMaxRedir = 5;
|
||||
protected float $actTimeout = 15.0;
|
||||
protected string $actUAgent = 'ForkBB downloader (%s)';
|
||||
protected array $actHeader = [
|
||||
|
@ -1282,6 +1291,8 @@ class Files
|
|||
return false;
|
||||
}
|
||||
|
||||
\curl_setopt($ch, \CURLOPT_PROTOCOLS, \CURLPROTO_HTTPS | \CURLPROTO_HTTP);
|
||||
\curl_setopt($ch, \CURLOPT_REDIR_PROTOCOLS, \CURLPROTO_HTTPS);
|
||||
\curl_setopt($ch, \CURLOPT_HTTPGET, true);
|
||||
\curl_setopt($ch, \CURLOPT_HEADER, false);
|
||||
\curl_setopt($ch, \CURLOPT_HTTPHEADER, $this->actHeader);
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace ForkBB\Core;
|
|||
use ForkBB\Core\Container;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Transliterator;
|
||||
use function \ForkBB\__;
|
||||
|
||||
class Func
|
||||
|
@ -37,8 +38,24 @@ class Func
|
|||
*/
|
||||
protected ?int $offset = null;
|
||||
|
||||
/**
|
||||
* Копия $this->c->FRIENDLY_URL
|
||||
*/
|
||||
protected array $fUrl;
|
||||
|
||||
/**
|
||||
* Для кэширования транслитератора
|
||||
*/
|
||||
protected Transliterator|false|null $transl = null;
|
||||
|
||||
/**
|
||||
* Массив подмены символов перед/вместо траслитератор(ом/а)
|
||||
*/
|
||||
protected ?array $translArray = null;
|
||||
|
||||
public function __construct(protected Container $c)
|
||||
{
|
||||
$this->fUrl = $c->isInit('FRIENDLY_URL') ? $c->FRIENDLY_URL : [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,19 +325,31 @@ class Func
|
|||
*/
|
||||
public function friendly(string $str): string
|
||||
{
|
||||
$conf = $this->c->FRIENDLY_URL;
|
||||
$rule = $conf['translit'];
|
||||
if (! empty($this->fUrl['translit'])) {
|
||||
if (! empty($this->fUrl['file'])) {
|
||||
$this->translArray ??= include "{$this->c->DIR_CONFIG}/{$this->fUrl['file']}";
|
||||
|
||||
if (true === $conf['lowercase']) {
|
||||
$rule .= 'Lower();';
|
||||
$str = \strtr($str, $this->translArray);
|
||||
}
|
||||
|
||||
if (
|
||||
\is_string($this->fUrl['translit'])
|
||||
&& \preg_match('%[\x80-\xFF]%', $str)
|
||||
) {
|
||||
$this->transl ??= Transliterator::create($this->fUrl['translit']) ?? false;
|
||||
|
||||
if ($this->transl instanceof Transliterator) {
|
||||
$str = $this->transl->transliterate($str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('' !== $rule) {
|
||||
$str = \transliterator_transliterate($rule, $str);
|
||||
if (true === $this->fUrl['lowercase']) {
|
||||
$str = \mb_strtolower($str, 'UTF-8');
|
||||
}
|
||||
|
||||
if (true === $conf['WtoHyphen']) {
|
||||
$str = \trim(\preg_replace(['%[^\w-]+%u', '%_+%'], ['-', '_'], $str), '-_');
|
||||
if (true === $this->fUrl['WtoHyphen']) {
|
||||
$str = \trim(\preg_replace(['%[^\w]+%u', '%_+%'], ['-', '_'], $str), '-_');
|
||||
}
|
||||
|
||||
return isset($str[0]) ? $str : '-';
|
||||
|
|
|
@ -37,7 +37,7 @@ class Mail
|
|||
|
||||
public function __construct(string $host, string $user, #[SensitiveParameter] string $pass, int $ssl, string $eol, protected Container $c)
|
||||
{
|
||||
if ('' != $host) {
|
||||
if ('' !== $host) {
|
||||
$hp = \explode(':', $host, 2);
|
||||
|
||||
if (
|
||||
|
@ -103,11 +103,10 @@ class Mail
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
if ($strict) {
|
||||
if (true === $strict) {
|
||||
$level = $this->c->ErrorHandler->logOnly(\E_WARNING);
|
||||
|
||||
if ($ip) {
|
||||
if (\is_string($ip)) {
|
||||
$mx = \checkdnsrr($ip, 'MX'); // ipv6 в пролёте :(
|
||||
} else {
|
||||
$mx = \dns_get_record($domainASCII, \DNS_MX);
|
||||
|
|
|
@ -149,7 +149,9 @@ class Router
|
|||
'page' !== $name
|
||||
|| 1 !== $args[$name]
|
||||
) {
|
||||
$data['{' . $name . '}'] = \rawurlencode(\str_replace($this->subSearch, $this->subRepl, (string) $args[$name]));
|
||||
$data['{' . $name . '}'] = \is_integer($args[$name])
|
||||
? (string) $args[$name]
|
||||
: \rawurlencode(\str_replace($this->subSearch, $this->subRepl, (string) $args[$name]));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -232,7 +234,12 @@ class Router
|
|||
}
|
||||
|
||||
$pos = \strpos($uri, '/', 1);
|
||||
$base = false === $pos ? $uri : \substr($uri, 0, $pos);
|
||||
|
||||
if (false === $pos) {
|
||||
$base = isset($this->dynamic[$uri]) ? $uri : '/';
|
||||
} else {
|
||||
$base = \substr($uri, 0, $pos);
|
||||
}
|
||||
|
||||
if (isset($this->dynamic[$base])) {
|
||||
foreach ($this->dynamic[$base] as $pattern => $data) {
|
||||
|
|
|
@ -140,13 +140,7 @@ class Validator
|
|||
public function addRules(array $list): Validator
|
||||
{
|
||||
foreach ($list as $field => $raw) {
|
||||
$rules = [];
|
||||
$suffix = null;
|
||||
|
||||
// правило для элементов массива
|
||||
if (\strpos($field, '.') > 0) {
|
||||
list($field, $suffix) = \explode('.', $field, 2);
|
||||
}
|
||||
$rules = [];
|
||||
|
||||
if (! \is_array($raw)) {
|
||||
$raw = \explode('|', $raw);
|
||||
|
@ -182,13 +176,34 @@ class Validator
|
|||
$rules[$name] = $rule ?? '';
|
||||
}
|
||||
|
||||
if (isset($suffix)) {
|
||||
$this->rules[$field]['array'][$suffix] = $rules;
|
||||
} else {
|
||||
$this->rules[$field] = $rules;
|
||||
}
|
||||
if (\strpos($field, '.') > 0) {
|
||||
$fields = \explode('.', $field);
|
||||
$n = \count($fields);
|
||||
$start = true;
|
||||
$r = &$this->rules;
|
||||
|
||||
$this->fields[$field] = $field;
|
||||
foreach ($fields as $field) {
|
||||
if (true === $start) {
|
||||
$this->fields[$field] = $field;
|
||||
$start = false;
|
||||
}
|
||||
|
||||
if (--$n) {
|
||||
if (! isset($r[$field]['array'])) {
|
||||
$r[$field]['array'] = [];
|
||||
}
|
||||
|
||||
$r = &$r[$field]['array'];
|
||||
} else {
|
||||
$r[$field] = $rules;
|
||||
}
|
||||
}
|
||||
|
||||
unset ($r);
|
||||
} else {
|
||||
$this->rules[$field] = $rules;
|
||||
$this->fields[$field] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -639,35 +654,32 @@ class Validator
|
|||
if ('' === $name) {
|
||||
$result = $this->checkValue($value, $rules, $field);
|
||||
} else {
|
||||
if (! \preg_match('%^([^.]+)(?:\.(.+))?$%', $name, $matches)) {
|
||||
if (false !== \strpos($name, '.')) {
|
||||
throw new RuntimeException("Bad path '{$name}'");
|
||||
}
|
||||
|
||||
$key = $matches[1];
|
||||
$name = $matches[2] ?? '';
|
||||
|
||||
if (
|
||||
'*' === $key
|
||||
'*' === $name
|
||||
&& \is_array($value)
|
||||
) {
|
||||
foreach ($value as $i => $cur) {
|
||||
$this->recArray($value[$i], $result[$i], $name, $rules, $field);
|
||||
$this->recArray($value[$i], $result[$i], '', $rules, $field);
|
||||
}
|
||||
} elseif (
|
||||
'*' !== $key
|
||||
'*' !== $name
|
||||
&& \is_array($value)
|
||||
&& \array_key_exists($key, $value)
|
||||
&& \array_key_exists($name, $value)
|
||||
) {
|
||||
$this->recArray($value[$key], $result[$key], $name, $rules, $field);
|
||||
$this->recArray($value[$name], $result[$name], '', $rules, $field);
|
||||
} elseif (isset($rules['required'])) {
|
||||
$tmp1 = null;
|
||||
$tmp2 = null;
|
||||
$this->recArray($tmp1, $tmp2, $name, $rules, $field);
|
||||
} elseif ('*' === $key) {
|
||||
$this->recArray($tmp1, $tmp2, '', $rules, $field);
|
||||
} elseif ('*' === $name) {
|
||||
$result = []; // ???? а может там не отсутствие элемента, а не array?
|
||||
} else {
|
||||
$value[$key] = null;
|
||||
$this->recArray($value[$key], $result[$key], $name, $rules, $field);
|
||||
$value[$name] = null;
|
||||
$this->recArray($value[$name], $result[$name], '', $rules, $field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,8 +66,6 @@ class BBCodeList extends Model
|
|||
{
|
||||
if (\function_exists('\\opcache_invalidate')) {
|
||||
\opcache_invalidate($this->fileCache, true);
|
||||
} elseif (\function_exists('\\apc_delete_file')) {
|
||||
\apc_delete_file($this->fileCache);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
|
@ -161,7 +161,10 @@ class Extension extends Model
|
|||
|
||||
$path = $this->fileData['path'] . '/' . \ltrim($cur['file'], '\\/');
|
||||
|
||||
if (! \is_file($path)) {
|
||||
if (
|
||||
$this->c->Files->isBadPath($path)
|
||||
|| ! \is_file($path)
|
||||
) {
|
||||
return ['Template file \'%s\' not found', $cur['file']];
|
||||
}
|
||||
|
||||
|
@ -181,6 +184,49 @@ class Extension extends Model
|
|||
}
|
||||
}
|
||||
|
||||
if ($this->fileData['extra']['symlinks']) {
|
||||
foreach ($this->fileData['extra']['symlinks'] as $cur) {
|
||||
switch($cur['type']) {
|
||||
case 'public':
|
||||
if (
|
||||
empty($cur['target'])
|
||||
|| empty($cur['link'])
|
||||
|| $this->c->Files->isBadPath($cur['target'])
|
||||
|| $this->c->Files->isBadPath($cur['link'])
|
||||
) {
|
||||
return 'Bad symlink';
|
||||
}
|
||||
|
||||
$target = $this->fileData['path'] . '/' . \trim($cur['target'], '\\/');
|
||||
|
||||
if (
|
||||
! \is_file($target)
|
||||
&& ! \is_dir($target)
|
||||
) {
|
||||
return ['Target \'%s\' not found', $cur['target']];
|
||||
}
|
||||
|
||||
$link = $this->c->DIR_PUBLIC . '/' . \trim($cur['link'], '\\/');
|
||||
|
||||
if (
|
||||
! \is_link($link)
|
||||
&& (
|
||||
\is_file($link)
|
||||
|| \is_dir($link)
|
||||
)
|
||||
) {
|
||||
return ['Link \'%s\' already exists', $cur['link']];
|
||||
}
|
||||
|
||||
$this->prepareData['symlinks'][$target] = $link;
|
||||
|
||||
break;
|
||||
default:
|
||||
return 'Invalid symlink type';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -150,6 +150,10 @@ class Extensions extends Manager
|
|||
'extra' => 'required|array',
|
||||
'extra.display-name' => 'required|string',
|
||||
'extra.requirements' => 'array',
|
||||
'extra.symlinks' => 'array',
|
||||
'extra.symlinks.*.type' => 'required|string|in:public',
|
||||
'extra.symlinks.*.target' => 'required|string',
|
||||
'extra.symlinks.*.link' => 'required|string',
|
||||
'extra.templates' => 'array',
|
||||
'extra.templates.*.type' => 'required|string|in:pre',
|
||||
'extra.templates.*.template' => 'required|string',
|
||||
|
@ -164,15 +168,28 @@ class Extensions extends Manager
|
|||
$result = [];
|
||||
|
||||
foreach ($files as $path => $file) {
|
||||
$context = null;
|
||||
|
||||
if (! \is_array($file)) {
|
||||
continue;
|
||||
$context = [
|
||||
'errors' => ['Bad json'],
|
||||
];
|
||||
} elseif (! $v->validation($file)) {
|
||||
continue;
|
||||
$context = [
|
||||
'errors' => \array_map('\\ForkBB\__', $v->getErrorsWithoutType()),
|
||||
];
|
||||
}
|
||||
|
||||
$data = $v->getData(true);
|
||||
$data['path'] = $path;
|
||||
$result[$v->name] = $data;
|
||||
if (null === $context) {
|
||||
$data = $v->getData(true);
|
||||
$data['path'] = $path;
|
||||
$result[$v->name] = $data;
|
||||
} else {
|
||||
$context['headers'] = false;
|
||||
$path = \preg_replace('%^.+((?:[\\\\/]+[^\\\\/]+){3})$%', '$1', $path);
|
||||
|
||||
$this->c->Log->debug("Extension: Bad structure for {$path}", $context);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@ -238,6 +255,7 @@ class Extensions extends Manager
|
|||
return false;
|
||||
}
|
||||
|
||||
$this->setSymlinks($ext);
|
||||
$this->updateIndividual();
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
|
@ -272,6 +290,8 @@ class Extensions extends Manager
|
|||
'fileData' => $ext->fileData,
|
||||
]);
|
||||
|
||||
$this->removeSymlinks($ext);
|
||||
|
||||
if (true !== $this->updateCommon($ext)) {
|
||||
$this->error = 'An error occurred in updateCommon';
|
||||
|
||||
|
@ -340,6 +360,10 @@ class Extensions extends Manager
|
|||
'fileData' => $ext->fileData,
|
||||
]);
|
||||
|
||||
if ($oldStatus) {
|
||||
$this->removeSymlinks($ext);
|
||||
}
|
||||
|
||||
if (true !== $this->updateCommon($ext)) {
|
||||
$this->error = 'An error occurred in updateCommon';
|
||||
|
||||
|
@ -347,6 +371,7 @@ class Extensions extends Manager
|
|||
}
|
||||
|
||||
if ($oldStatus) {
|
||||
$this->setSymlinks($ext);
|
||||
$this->updateIndividual();
|
||||
}
|
||||
|
||||
|
@ -379,6 +404,7 @@ class Extensions extends Manager
|
|||
'fileData' => $ext->fileData,
|
||||
]);
|
||||
|
||||
$this->setSymlinks($ext);
|
||||
$this->updateIndividual();
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
|
@ -410,6 +436,7 @@ class Extensions extends Manager
|
|||
'fileData' => $ext->fileData,
|
||||
]);
|
||||
|
||||
$this->removeSymlinks($ext);
|
||||
$this->updateIndividual();
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
|
@ -457,8 +484,6 @@ class Extensions extends Manager
|
|||
} else {
|
||||
if (\function_exists('\\opcache_invalidate')) {
|
||||
\opcache_invalidate($file, true);
|
||||
} elseif (\function_exists('\\apc_delete_file')) {
|
||||
\apc_delete_file($file);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -544,4 +569,37 @@ class Extensions extends Manager
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает симлинки для расширения
|
||||
*/
|
||||
protected function setSymlinks(Extension $ext): bool
|
||||
{
|
||||
$data = $this->loadDataFromFile($this->commonFile);
|
||||
$symlinks = $data[$ext->name]['symlinks'] ?? [];
|
||||
|
||||
foreach ($symlinks as $target => $link) {
|
||||
\symlink($target, $link);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет симлинки расширения
|
||||
*/
|
||||
protected function removeSymlinks(Extension $ext): bool
|
||||
{
|
||||
$data = $this->loadDataFromFile($this->commonFile);
|
||||
$symlinks = $data[$ext->name]['symlinks'] ?? [];
|
||||
|
||||
foreach ($symlinks as $target => $link) {
|
||||
if (\is_link($link)) {
|
||||
\is_file($link) ? \unlink($link) : \rmdir($link);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class Delete extends Action
|
|||
$uids[$arg->id] = $arg->id;
|
||||
$isUser = 1;
|
||||
} elseif ($arg instanceof Forum) {
|
||||
if (! $this->c->forums->get($arg->id) instanceof Forum) {
|
||||
if (! $this->manager->get($arg->id) instanceof Forum) {
|
||||
throw new RuntimeException('Forum unavailable');
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class Forum extends DataModel
|
|||
|
||||
return null;
|
||||
} else {
|
||||
return $this->c->forums->get($this->parent_forum_id);
|
||||
return $this->manager->get($this->parent_forum_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,14 @@ class Forum extends DataModel
|
|||
return $this->forum_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает название для формирования URL
|
||||
*/
|
||||
protected function getfriendly(): ?string
|
||||
{
|
||||
return isset($this->friendly_name[0]) ? $this->friendly_name : $this->forum_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Статус возможности создания новой темы
|
||||
*/
|
||||
|
@ -93,7 +101,7 @@ class Forum extends DataModel
|
|||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as $id) {
|
||||
$sub[$id] = $this->c->forums->get($id);
|
||||
$sub[$id] = $this->manager->get($id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +118,7 @@ class Forum extends DataModel
|
|||
|
||||
if (\is_array($attr)) {
|
||||
foreach ($attr as $id) {
|
||||
$all[$id] = $this->c->forums->get($id);
|
||||
$all[$id] = $this->manager->get($id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +137,7 @@ class Forum extends DataModel
|
|||
'Forum',
|
||||
[
|
||||
'id' => $this->id,
|
||||
'name' => $this->c->Func->friendly($this->forum_name),
|
||||
'name' => $this->friendly,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -352,7 +360,7 @@ class Forum extends DataModel
|
|||
}
|
||||
}
|
||||
|
||||
$attr = $this->c->forums->create([
|
||||
$attr = $this->manager->create([
|
||||
'num_topics' => $numT,
|
||||
'num_posts' => $numP,
|
||||
'last_post' => $time,
|
||||
|
@ -391,7 +399,7 @@ class Forum extends DataModel
|
|||
'Forum',
|
||||
[
|
||||
'id' => $this->id,
|
||||
'name' => $this->c->Func->friendly($this->forum_name),
|
||||
'name' => $this->friendly,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class Forums extends Manager
|
|||
*/
|
||||
public function create(array $attrs = []): Forum
|
||||
{
|
||||
return $this->c->ForumModel->setModelAttrs($attrs);
|
||||
return $this->c->ForumModel->setManager($this)->setModelAttrs($attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,7 +37,7 @@ class Refresh extends Action
|
|||
$vars = [
|
||||
':gid' => $gid,
|
||||
];
|
||||
$query = 'SELECT f.cat_id, c.cat_name, f.id, f.forum_name, f.redirect_url, f.parent_forum_id,
|
||||
$query = 'SELECT f.cat_id, c.cat_name, f.id, f.forum_name, f.friendly_name, f.redirect_url, f.parent_forum_id,
|
||||
f.moderators, f.no_sum_mess, f.disp_position, f.sort_by, fp.post_topics, fp.post_replies
|
||||
FROM ::categories AS c
|
||||
INNER JOIN ::forums AS f ON c.id=f.cat_id
|
||||
|
|
|
@ -44,7 +44,7 @@ class UpdateUsername extends Action
|
|||
$isMod = true;
|
||||
$forum->modAdd($user); // переименование модератора
|
||||
|
||||
$this->c->forums->update($forum);
|
||||
$this->manager->update($forum);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||
namespace ForkBB\Models;
|
||||
|
||||
use ForkBB\Core\Container;
|
||||
use ForkBB\Models\Manager;
|
||||
|
||||
class Model
|
||||
{
|
||||
|
@ -34,6 +35,11 @@ class Model
|
|||
*/
|
||||
protected array $zDepend = [];
|
||||
|
||||
/**
|
||||
* Текущий Manager для модели
|
||||
*/
|
||||
protected Manager $manager;
|
||||
|
||||
public function __construct(protected Container $c)
|
||||
{
|
||||
}
|
||||
|
@ -165,4 +171,14 @@ class Model
|
|||
|
||||
return $this->c->$key->setModel($this)->$name(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Объявление менеджера
|
||||
*/
|
||||
public function setManager(Manager $manager): Model
|
||||
{
|
||||
$this->manager = $manager;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ class Categories extends Admin
|
|||
$v = $this->c->Validator->reset()
|
||||
->addRules([
|
||||
'token' => 'token:AdminCategories',
|
||||
'form' => 'required|array',
|
||||
'form.*.cat_name' => 'required|string:trim|max:80',
|
||||
'form.*.disp_position' => 'required|integer|min:0|max:9999999999',
|
||||
'new' => 'exist|string:trim|max:80'
|
||||
|
|
|
@ -29,8 +29,9 @@ class Censoring extends Admin
|
|||
->addRules([
|
||||
'token' => 'token:AdminCensoring',
|
||||
'b_censoring' => 'required|integer|in:0,1',
|
||||
'form.*.search_for' => 'string:trim|max:60',
|
||||
'form.*.replace_with' => 'string:trim|max:60',
|
||||
'form' => 'required|array',
|
||||
'form.*.search_for' => 'exist|string:trim|max:60',
|
||||
'form.*.replace_with' => 'exist|string:trim|max:60',
|
||||
])->addAliases([
|
||||
])->addArguments([
|
||||
])->addMessages([
|
||||
|
|
|
@ -102,6 +102,7 @@ class Forums extends Admin
|
|||
$v = $this->c->Validator->reset()
|
||||
->addRules([
|
||||
'token' => 'token:AdminForums',
|
||||
'form' => 'required|array',
|
||||
'form.*.disp_position' => 'required|integer|min:0|max:9999999999',
|
||||
])->addAliases([
|
||||
])->addArguments([
|
||||
|
@ -362,10 +363,11 @@ class Forums extends Admin
|
|||
->addRules([
|
||||
'token' => 'token:' . $marker,
|
||||
'forum_name' => 'required|string:trim|max:80',
|
||||
'friendly_name' => 'string:trim|max:80|regex:%^[\w-]*$%',
|
||||
'forum_desc' => 'exist|string:trim|max:65000 bytes|html',
|
||||
'parent' => 'required|integer|in:' . \implode(',', $this->listOfIndexes),
|
||||
'sort_by' => 'required|integer|in:0,1,2,4,5,6',
|
||||
'redirect_url' => 'string:trim|max:255', //???? это поле может быть отключено в форме
|
||||
'redirect_url' => 'string:trim|max:255|regex:%^(?:https?://.+)?$%', //???? это поле может быть отключено в форме
|
||||
'no_sum_mess' => 'required|integer|in:0,1',
|
||||
'perms.*.read_forum' => 'checkbox',
|
||||
'perms.*.post_replies' => 'checkbox',
|
||||
|
@ -379,11 +381,12 @@ class Forums extends Admin
|
|||
|
||||
$valid = $v->validation($_POST);
|
||||
|
||||
$forum->forum_name = $v->forum_name;
|
||||
$forum->forum_desc = $v->forum_desc;
|
||||
$forum->sort_by = $v->sort_by;
|
||||
$forum->redirect_url = $v->redirect_url ?? '';
|
||||
$forum->no_sum_mess = $v->no_sum_mess;
|
||||
$forum->forum_name = $v->forum_name;
|
||||
$forum->friendly_name = \trim($v->friendly_name, '_-');
|
||||
$forum->forum_desc = $v->forum_desc;
|
||||
$forum->sort_by = $v->sort_by;
|
||||
$forum->redirect_url = $v->redirect_url ?? '';
|
||||
$forum->no_sum_mess = $v->no_sum_mess;
|
||||
|
||||
if ($v->parent > 0) {
|
||||
$forum->parent_forum_id = $v->parent;
|
||||
|
@ -462,6 +465,13 @@ class Forums extends Admin
|
|||
'caption' => 'Forum name label',
|
||||
'required' => true,
|
||||
],
|
||||
'friendly_name' => [
|
||||
'type' => 'text',
|
||||
'maxlength' => '80',
|
||||
'value' => $forum->friendly_name,
|
||||
'caption' => 'Friendly name label',
|
||||
'help' => 'Friendly name help',
|
||||
],
|
||||
'forum_desc' => [
|
||||
'type' => 'textarea',
|
||||
'value' => $forum->forum_desc,
|
||||
|
|
|
@ -839,6 +839,7 @@ class Install extends Admin
|
|||
'FIELDS' => [
|
||||
'id' => ['SERIAL', false],
|
||||
'forum_name' => ['VARCHAR(80)', false, 'New forum'],
|
||||
'friendly_name' => ['VARCHAR(80)', false, ''],
|
||||
'forum_desc' => ['TEXT', false],
|
||||
'redirect_url' => ['VARCHAR(255)', false, ''],
|
||||
'moderators' => ['TEXT', false],
|
||||
|
|
|
@ -331,6 +331,10 @@ class Maintenance extends Admin
|
|||
throw new RuntimeException('Unable to clear cache');
|
||||
}
|
||||
|
||||
if (\function_exists('\\opcache_reset')) {
|
||||
\opcache_reset();
|
||||
}
|
||||
|
||||
return $this->c->Redirect->page('AdminMaintenance')->message('Clear cache redirect', FORK_MESS_SUCC);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class Update extends Admin
|
|||
{
|
||||
const PHP_MIN = '8.0.0';
|
||||
const REV_MIN_FOR_UPDATE = 53;
|
||||
const LATEST_REV_WITH_DB_CHANGES = 70;
|
||||
const LATEST_REV_WITH_DB_CHANGES = 72;
|
||||
const LOCK_NAME = 'lock_update';
|
||||
const LOCK_TTL = 1800;
|
||||
const CONFIG_FILE = 'main.php';
|
||||
|
@ -1001,4 +1001,69 @@ class Update extends Admin
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* rev.71 to rev.72
|
||||
*/
|
||||
protected function stageNumber71(array $args): ?int
|
||||
{
|
||||
switch ($args['start'] ?? 1) {
|
||||
case 3:
|
||||
$f = $this->c->FRIENDLY_URL;
|
||||
|
||||
if (
|
||||
! empty($f['lowercase'])
|
||||
|| ! empty($f['translit'])
|
||||
|| ! empty($f['WtoHyphen'])
|
||||
) {
|
||||
|
||||
$names = $this->c->DB->query('SELECT id, forum_name FROM ::forums WHERE redirect_url=\'\' ORDER BY id')->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$query = 'UPDATE ::forums SET friendly_name=?s:name WHERE id=?i:id';
|
||||
|
||||
foreach ($names as $id => $name) {
|
||||
$vars = [
|
||||
':id' => $id,
|
||||
':name' => \mb_substr($this->c->Func->friendly($name), 0, 80, 'UTF-8'),
|
||||
];
|
||||
$this->c->DB->exec($query, $vars);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
case 2:
|
||||
$this->c->DB->addField('::forums', 'friendly_name', 'VARCHAR(80)', false, '', null, 'forum_name');
|
||||
|
||||
return 3;
|
||||
default:
|
||||
$coreConfig = new CoreConfig($this->configFile);
|
||||
|
||||
$coreConfig->add(
|
||||
'FRIENDLY_URL=>file',
|
||||
'\'translit.default.php\'',
|
||||
'WtoHyphen'
|
||||
);
|
||||
|
||||
$coreConfig->save();
|
||||
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* rev.72 to rev.73
|
||||
*/
|
||||
protected function stageNumber72(array $args): ?int
|
||||
{
|
||||
$coreConfig = new CoreConfig($this->configFile);
|
||||
|
||||
$coreConfig->add(
|
||||
'multiple=>Sitemap',
|
||||
'\\ForkBB\\Models\\Pages\\Sitemap::class',
|
||||
'Misc'
|
||||
);
|
||||
|
||||
$coreConfig->save();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class Forum extends Page
|
|||
'Forum',
|
||||
[
|
||||
'id' => $args['id'],
|
||||
'name' => $this->c->Func->friendly($forum->forum_name),
|
||||
'name' => $forum->friendly,
|
||||
'page' => $forum->page,
|
||||
]
|
||||
);
|
||||
|
@ -68,7 +68,10 @@ class Forum extends Page
|
|||
$this->formMod = $this->formMod($forum);
|
||||
}
|
||||
|
||||
if ($this->c->config->i_feed_type > 0) {
|
||||
if (
|
||||
$this->c->config->i_feed_type > 0
|
||||
&& $forum->num_posts > 0
|
||||
) {
|
||||
$feedType = 2 === $this->c->config->i_feed_type ? 'atom' : 'rss';
|
||||
|
||||
$this->pageHeader('feed', 'link', 0, [
|
||||
|
@ -156,7 +159,7 @@ class Forum extends Page
|
|||
'Forum',
|
||||
[
|
||||
'id' => $forum->id,
|
||||
'name' => $forum->forum_name,
|
||||
'name' => $forum->friendly,
|
||||
'page' => $forum->page,
|
||||
'#' => "topic-{$topic->id}",
|
||||
]
|
||||
|
|
|
@ -284,7 +284,7 @@ class Moderate extends Page
|
|||
'Forum',
|
||||
[
|
||||
'id' => $this->curForum->id,
|
||||
'name' => $this->curForum->forum_name,
|
||||
'name' => $this->curForum->friendly,
|
||||
'page' => $v->page,
|
||||
]
|
||||
);
|
||||
|
@ -613,7 +613,7 @@ class Moderate extends Page
|
|||
$delLinks = [];
|
||||
|
||||
foreach ($this->c->forums->depthList($root, 0) as $forum) {
|
||||
if ('' != $forum->redirect_url) {
|
||||
if ($forum->redirect_url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -720,7 +720,7 @@ class Moderate extends Page
|
|||
'type' => 'checkbox',
|
||||
'value' => $forum->id,
|
||||
'checked' => ! empty($ft[$forum->id]),
|
||||
'disabled' => '' != $forum->redirect_url,
|
||||
'disabled' => ! empty($forum->redirect_url),
|
||||
'caption' => 'Redir label',
|
||||
];
|
||||
$form['sets']["forum{$forum->id}"] = [
|
||||
|
|
|
@ -31,7 +31,7 @@ abstract class AbstractPM extends Page
|
|||
$this->fIndex = self::FI_PM;
|
||||
$this->onlinePos = 'pm';
|
||||
$this->robots = 'noindex, nofollow';
|
||||
$this->hhsLevel = 'secure';
|
||||
// $this->hhsLevel = 'secure';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,12 +38,14 @@ class Poll extends Page
|
|||
->addValidators([
|
||||
])->addRules([
|
||||
'token' => 'token:Poll',
|
||||
'poll_vote' => 'required|array',
|
||||
'poll_vote.*.*' => 'required|integer',
|
||||
'vote' => 'required|string',
|
||||
])->addAliases([
|
||||
])->addArguments([
|
||||
'token' => $args,
|
||||
])->addMessages([
|
||||
'poll_vote' => 'The poll structure is broken',
|
||||
'poll_vote.*.*' => 'The poll structure is broken',
|
||||
]);
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ class Mod extends Profile
|
|||
'type' => 'checkbox',
|
||||
'value' => $forum->id,
|
||||
'checked' => isset($this->curForums[$forum->id]) && $this->curUser->isModerator($forum),
|
||||
'disabled' => ! isset($this->curForums[$forum->id]) || '' != $this->curForums[$forum->id]->redirect_url,
|
||||
'disabled' => ! isset($this->curForums[$forum->id]) || ! empty($this->curForums[$forum->id]->redirect_url),
|
||||
'caption' => 'Moderator label',
|
||||
];
|
||||
$form['sets']["forum{$forum->id}"] = [
|
||||
|
|
|
@ -52,7 +52,15 @@ class Search extends Profile
|
|||
|
||||
if ($v->validation($_POST)) {
|
||||
if (! empty($v->follow)) {
|
||||
$unfollow = \array_diff(\array_keys($this->curForums), $v->follow);
|
||||
$unfollow = [];
|
||||
|
||||
foreach ($this->curForums as $id => $forum) {
|
||||
if (empty($forum->redirect_url)) {
|
||||
$unfollow[$id] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
$unfollow = \array_diff($unfollow, $v->follow);
|
||||
|
||||
\sort($unfollow, \SORT_NUMERIC);
|
||||
|
||||
|
@ -186,7 +194,7 @@ class Search extends Profile
|
|||
'type' => 'checkbox',
|
||||
'value' => $forum->id,
|
||||
'checked' => ! isset($this->curUnfollowed[$forum->id]),
|
||||
'disabled' => '' != $this->curForums[$forum->id]->redirect_url,
|
||||
'disabled' => ! empty($this->curForums[$forum->id]->redirect_url),
|
||||
'caption' => 'Follow label',
|
||||
];
|
||||
$form['sets']["forum{$forum->id}"] = [
|
||||
|
|
|
@ -257,7 +257,10 @@ class View extends Profile
|
|||
];
|
||||
|
||||
if ($this->curUser->last_post > 0) {
|
||||
if (1 === $this->user->g_search) {
|
||||
if (
|
||||
1 === $this->user->g_search
|
||||
&& ! $this->user->isBot
|
||||
) {
|
||||
$fields['posts'] = [
|
||||
'class' => ['pline'],
|
||||
'type' => 'link',
|
||||
|
@ -271,6 +274,7 @@ class View extends Profile
|
|||
]
|
||||
),
|
||||
'title' => __('Show posts'),
|
||||
'rel' => 'nofollow',
|
||||
];
|
||||
$fields['topics'] = [
|
||||
'class' => ['pline'],
|
||||
|
@ -285,6 +289,7 @@ class View extends Profile
|
|||
]
|
||||
),
|
||||
'title' => __('Show topics'),
|
||||
'rel' => 'nofollow',
|
||||
];
|
||||
} elseif ($this->userRules->showPostCount) {
|
||||
$fields['posts'] = [
|
||||
|
|
|
@ -501,7 +501,10 @@ class Search extends Page
|
|||
case 'topics':
|
||||
case 'topics_subscriptions':
|
||||
case 'forums_subscriptions':
|
||||
if (! isset($uid)) {
|
||||
if (
|
||||
! isset($uid)
|
||||
|| $this->user->isBot
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
196
app/Models/Pages/Sitemap.php
Normal file
196
app/Models/Pages/Sitemap.php
Normal file
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Models\Pages;
|
||||
|
||||
use ForkBB\Models\Page;
|
||||
use ForkBB\Models\Forum\Forum;
|
||||
use ForkBB\Models\Forum\Forums;
|
||||
use ForkBB\Models\Group\Group;
|
||||
|
||||
class Sitemap extends Page
|
||||
{
|
||||
|
||||
public array $sitemap = [];
|
||||
|
||||
/**
|
||||
* Вывод sitemap
|
||||
*/
|
||||
public function view(array $args): Page
|
||||
{
|
||||
$this->nameTpl = 'sitemap';
|
||||
$this->onlinePos = 'sitemap';
|
||||
$this->onlineDetail = null;
|
||||
|
||||
$gGroup = $this->c->groups->get(FORK_GROUP_GUEST);
|
||||
$forums = $this->c->ForumManager->init($gGroup);
|
||||
$max = 50000;
|
||||
|
||||
if (1 === $gGroup->g_read_board) {
|
||||
$result = match ($args['id']) {
|
||||
null => $this->sitemap($forums, $gGroup, $max),
|
||||
'0' => $this->sitemap0($forums, $gGroup, $max),
|
||||
'00' => $this->sitemap00($forums, $gGroup, $max),
|
||||
default => $this->sitemapN($forums, $gGroup, $max, $args['id']),
|
||||
};
|
||||
}
|
||||
|
||||
$d = \number_format(\microtime(true) - $this->c->START, 3);
|
||||
|
||||
$this->c->Log->debug("{$this->nameTpl} : {$args['id']} : time = {$d}", [
|
||||
'user' => $this->user->fLog(),
|
||||
'headers' => true,
|
||||
]);
|
||||
|
||||
if (empty($this->sitemap)) {
|
||||
return $this->c->Message->message('Bad request');
|
||||
} else {
|
||||
$this->header('Content-type', 'application/xml; charset=utf-8');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
protected function sitemap(Forums $forums, Group $gGroup, int $max): bool
|
||||
{
|
||||
foreach ($forums->loadTree(0)->descendants as $forum) {
|
||||
if ($forum->last_post > 0) {
|
||||
$this->sitemap[$this->c->Router->link('Sitemap', ['id' => $forum->id])] = $forum->last_post;
|
||||
}
|
||||
}
|
||||
|
||||
if (1 === $gGroup->g_view_users) {
|
||||
$this->sitemap[$this->c->Router->link('Sitemap', ['id' => '0'])] = null;
|
||||
}
|
||||
|
||||
$this->sitemap[$this->c->Router->link('Sitemap', ['id' => '00'])] = null;
|
||||
|
||||
$this->nameTpl = 'sitemap_index';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function sitemap00(Forums $forums, Group $gGroup, int $max): bool
|
||||
{
|
||||
$this->sitemap[$this->c->Router->link('Index')] = null;
|
||||
|
||||
--$max;
|
||||
|
||||
if (
|
||||
1 === $this->c->config->b_rules
|
||||
&& 1 === $this->c->config->b_regs_allow
|
||||
) {
|
||||
$this->sitemap[$this->c->Router->link('Rules')] = null;
|
||||
|
||||
--$max;
|
||||
}
|
||||
|
||||
$dtd = $this->c->config->i_disp_topics_default;
|
||||
|
||||
foreach ($forums->loadTree(0)->descendants as $forum) {
|
||||
if ($forum->last_post > 0) {
|
||||
$pages = (int) \ceil(($forum->num_topics ?: 1) / $dtd);
|
||||
$page = 1;
|
||||
|
||||
for (; $max > 0 && $page <= $pages; --$max, ++$page) {
|
||||
$this->sitemap[$this->c->Router->link(
|
||||
'Forum',
|
||||
[
|
||||
'id' => $forum->id,
|
||||
'name' => $forum->friendly,
|
||||
'page' => $page,
|
||||
]
|
||||
)] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function sitemap0(Forums $forums, Group $gGroup, int $max): bool
|
||||
{
|
||||
if (1 !== $gGroup->g_view_users) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$vars = [
|
||||
':max' => $max,
|
||||
];
|
||||
$query = 'SELECT u.id, u.username
|
||||
FROM ::users AS u
|
||||
WHERE u.last_post!=0
|
||||
ORDER BY u.id DESC
|
||||
LIMIT ?i:max';
|
||||
|
||||
$stmt = $this->c->DB->query($query, $vars);
|
||||
|
||||
while ($cur = $stmt->fetch()) {
|
||||
$name = $this->c->Func->friendly($cur['username']);
|
||||
|
||||
$this->sitemap[$this->c->Router->link(
|
||||
'User',
|
||||
[
|
||||
'id' => $cur['id'],
|
||||
'name' => $name,
|
||||
]
|
||||
)] = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function sitemapN(Forums $forums, Group $gGroup, int $max, string $raw): bool
|
||||
{
|
||||
if (! \preg_match('%^[1-9]\d*$%', $raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$id = (int) $raw;
|
||||
$forum = $forums->get($id);
|
||||
|
||||
if (! $forum instanceof Forum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dpd = $this->c->config->i_disp_posts_default;
|
||||
|
||||
$vars = [
|
||||
':fid' => $forum->id,
|
||||
];
|
||||
$query = 'SELECT t.id, t.subject, t.last_post, t.num_replies
|
||||
FROM ::topics AS t
|
||||
WHERE t.moved_to=0 AND t.forum_id=?i:fid
|
||||
ORDER BY t.last_post DESC';
|
||||
|
||||
$stmt = $this->c->DB->query($query, $vars);
|
||||
|
||||
while ($cur = $stmt->fetch()) {
|
||||
$name = $this->c->Func->friendly($cur['subject']);
|
||||
$page = (int) \ceil(($cur['num_replies'] + 1) / $dpd);
|
||||
$last = $cur['last_post'];
|
||||
|
||||
for (; $max > 0 && $page > 0; --$max, --$page) {
|
||||
$this->sitemap[$this->c->Router->link(
|
||||
'Topic',
|
||||
[
|
||||
'id' => $cur['id'],
|
||||
'name' => $name,
|
||||
'page' => $page,
|
||||
]
|
||||
)] = $last;
|
||||
|
||||
$last = null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ namespace ForkBB\Models\Post;
|
|||
use ForkBB\Models\Action;
|
||||
use ForkBB\Models\Topic\Topic;
|
||||
use ForkBB\Models\Forum\Forum;
|
||||
use PDO;
|
||||
|
||||
class Feed extends Action
|
||||
{
|
||||
|
@ -50,13 +51,28 @@ class Feed extends Action
|
|||
$vars = [
|
||||
':forums' => $ids,
|
||||
];
|
||||
$query = 'SELECT p.id as pid, p.poster as username, p.poster_id as uid, p.message as content,
|
||||
p.hide_smilies, p.posted, p.edited, t.id as tid, t.subject as topic_name, t.forum_id as fid
|
||||
$query = 'SELECT p.id
|
||||
FROM ::posts AS p
|
||||
INNER JOIN ::topics AS t ON t.id=p.topic_id
|
||||
WHERE t.forum_id IN (?ai:forums)
|
||||
ORDER BY p.id DESC
|
||||
LIMIT 50';
|
||||
|
||||
$ids = $this->c->DB->query($query, $vars)->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$vars = [
|
||||
':ids' => $ids,
|
||||
];
|
||||
$query = 'SELECT p.id as pid, p.poster as username, p.poster_id as uid, p.message as content,
|
||||
p.hide_smilies, p.posted, p.edited, t.id as tid, t.subject as topic_name, t.forum_id as fid
|
||||
FROM ::posts AS p
|
||||
INNER JOIN ::topics AS t ON t.id=p.topic_id
|
||||
WHERE p.id IN (?ai:ids)
|
||||
ORDER BY p.id DESC';
|
||||
}
|
||||
|
||||
return $this->c->DB->query($query, $vars)->fetchAll();
|
||||
|
|
|
@ -267,7 +267,9 @@ abstract class Driver extends Model
|
|||
break;
|
||||
}
|
||||
|
||||
\curl_setopt($ch, \CURLOPT_MAXREDIRS, 10);
|
||||
\curl_setopt($ch, \CURLOPT_PROTOCOLS, \CURLPROTO_HTTPS | \CURLPROTO_HTTP);
|
||||
\curl_setopt($ch, \CURLOPT_REDIR_PROTOCOLS, \CURLPROTO_HTTPS);
|
||||
\curl_setopt($ch, \CURLOPT_MAXREDIRS, 5);
|
||||
\curl_setopt($ch, \CURLOPT_TIMEOUT, 10);
|
||||
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
|
||||
\curl_setopt($ch, \CURLOPT_HEADER, false);
|
||||
|
|
|
@ -31,49 +31,66 @@ class ActionP extends Method
|
|||
return [];
|
||||
}
|
||||
|
||||
$query = null;
|
||||
|
||||
switch ($action) {
|
||||
case 'search':
|
||||
$list = $this->model->queryIds;
|
||||
|
||||
$this->model->numPages = (int) \ceil(($this->model->count($list) ?: 1) / $this->c->user->disp_posts);
|
||||
|
||||
break;
|
||||
case 'posts':
|
||||
$query = 'SELECT p.id
|
||||
$vars = [
|
||||
':forums' => $forums,
|
||||
':uid' => $uid,
|
||||
];
|
||||
$query = 'SELECT COUNT(p.id)
|
||||
FROM ::posts AS p
|
||||
INNER JOIN ::topics AS t ON t.id=p.topic_id
|
||||
WHERE p.poster_id=?i:uid AND t.forum_id IN (?ai:forums)
|
||||
ORDER BY p.posted DESC';
|
||||
WHERE p.poster_id=?i:uid AND t.forum_id IN (?ai:forums)';
|
||||
|
||||
$count = (int) $this->c->DB->query($query, $vars)->fetchColumn();
|
||||
|
||||
$this->model->numPages = (int) \ceil(($count ?: 1) / $this->c->user->disp_posts);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Unknown action: ' . $action);
|
||||
}
|
||||
|
||||
if (null !== $query) {
|
||||
$vars = [
|
||||
':forums' => $forums,
|
||||
':uid' => $uid,
|
||||
];
|
||||
|
||||
$list = $this->c->DB->query($query, $vars)->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
$this->model->numPages = (int) \ceil(($this->model->count($list) ?: 1) / $this->c->user->disp_posts);
|
||||
|
||||
// нет такой страницы в результате поиска
|
||||
if (! $this->model->hasPage()) {
|
||||
return false;
|
||||
// результат пуст
|
||||
} elseif (empty($list)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->model->idsList = $this->model->slice(
|
||||
$list,
|
||||
($this->model->page - 1) * $this->c->user->disp_posts,
|
||||
(int) $this->c->user->disp_posts
|
||||
);
|
||||
switch ($action) {
|
||||
case 'search':
|
||||
// результат пуст
|
||||
if (empty($list)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->model->idsList = $this->model->slice(
|
||||
$list,
|
||||
($this->model->page - 1) * $this->c->user->disp_posts,
|
||||
(int) $this->c->user->disp_posts
|
||||
);
|
||||
|
||||
break;
|
||||
case 'posts':
|
||||
$vars[':offset'] = ($this->model->page - 1) * $this->c->user->disp_posts;
|
||||
$vars[':rows'] = (int) $this->c->user->disp_posts;
|
||||
|
||||
$query = 'SELECT p.id
|
||||
FROM ::posts AS p
|
||||
INNER JOIN ::topics AS t ON t.id=p.topic_id
|
||||
WHERE p.poster_id=?i:uid AND t.forum_id IN (?ai:forums)
|
||||
ORDER BY p.posted DESC
|
||||
LIMIT ?i:rows OFFSET ?i:offset';
|
||||
|
||||
$this->model->idsList = $this->c->DB->query($query, $vars)->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->c->posts->view($this->model);
|
||||
}
|
||||
|
|
|
@ -78,8 +78,8 @@ class ActionT extends Method
|
|||
*/
|
||||
// упрощенный запрос для больших форумов, дополнительная обработка ниже
|
||||
$query = 'SELECT DISTINCT t.id, t.last_post
|
||||
FROM forum_topics AS t
|
||||
INNER JOIN forum_posts AS p ON t.id=p.topic_id
|
||||
FROM ::topics AS t
|
||||
INNER JOIN ::posts AS p ON t.id=p.topic_id
|
||||
WHERE t.forum_id IN (?ai:forums) AND t.moved_to=0 AND p.poster_id=?i:uid';
|
||||
|
||||
break;
|
||||
|
|
|
@ -70,7 +70,7 @@ if (
|
|||
$c->BASE_URL = \str_replace('https://', 'http://', $c->BASE_URL);
|
||||
}
|
||||
|
||||
$c->FORK_REVISION = 71;
|
||||
$c->FORK_REVISION = 73;
|
||||
$c->START = $forkStart;
|
||||
$c->PUBLIC_URL = $c->BASE_URL . $forkPublicPrefix;
|
||||
|
||||
|
|
|
@ -60,6 +60,12 @@ return [
|
|||
],
|
||||
'DATE_FORMATS' => ['Y-m-d', 'd M Y', 'Y-m-d', 'Y-d-m', 'd-m-Y', 'm-d-Y', 'M j Y', 'jS M Y'],
|
||||
'TIME_FORMATS' => ['H:i:s', 'H:i', 'H:i:s', 'H:i', 'g:i:s a', 'g:i a'],
|
||||
'FRIENDLY_URL' => [
|
||||
'lowercase' => true,
|
||||
'translit' => true, // 'Any-Latin;Latin-ASCII;',
|
||||
'WtoHyphen' => true,
|
||||
'file' => 'translit.default.php',
|
||||
],
|
||||
|
||||
'forConfig' => [
|
||||
'o_default_lang' => 'en',
|
||||
|
|
|
@ -76,8 +76,9 @@ return [
|
|||
'TIME_FORMATS' => ['H:i:s', 'H:i', 'H:i:s', 'H:i', 'g:i:s a', 'g:i a'],
|
||||
'FRIENDLY_URL' => [
|
||||
'lowercase' => true,
|
||||
'translit' => 'Russian-Latin/BGN;Any-Latin;Latin-ASCII;',
|
||||
'translit' => true, // 'Any-Latin;Latin-ASCII;',
|
||||
'WtoHyphen' => true,
|
||||
'file' => 'translit.default.php',
|
||||
],
|
||||
|
||||
'shared' => [
|
||||
|
@ -371,6 +372,7 @@ return [
|
|||
'Ban' => \ForkBB\Models\Pages\Ban::class,
|
||||
'Debug' => \ForkBB\Models\Pages\Debug::class,
|
||||
'Misc' => \ForkBB\Models\Pages\Misc::class,
|
||||
'Sitemap' => \ForkBB\Models\Pages\Sitemap::class,
|
||||
'Moderate' => \ForkBB\Models\Pages\Moderate::class,
|
||||
'Report' => \ForkBB\Models\Pages\Report::class,
|
||||
'Email' => \ForkBB\Models\Pages\Email::class,
|
||||
|
|
1121
app/config/translit.default.php
Normal file
1121
app/config/translit.default.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -119,3 +119,15 @@ msgstr "An error occurred in updateCommon."
|
|||
|
||||
msgid "Empty"
|
||||
msgstr "Empty"
|
||||
|
||||
msgid "Invalid symlink type"
|
||||
msgstr "Invalid symlink type."
|
||||
|
||||
msgid "Bad symlink"
|
||||
msgstr "Bad symlink."
|
||||
|
||||
msgid "Target '%s' not found"
|
||||
msgstr "Target '%s' not found."
|
||||
|
||||
msgid "Link '%s' already exists"
|
||||
msgstr "Link '%s' already exists."
|
||||
|
|
|
@ -134,3 +134,9 @@ msgstr "If YES, then new messages from users in this forum will increase their c
|
|||
|
||||
msgid "<span></span>"
|
||||
msgstr "<span></span>"
|
||||
|
||||
msgid "Friendly name label"
|
||||
msgstr "Name for URL"
|
||||
|
||||
msgid "Friendly name help"
|
||||
msgstr "A string identifier on the basis of which the url to this forum will be generated. May consist of Latin letters, numbers, underscores and hyphens."
|
||||
|
|
|
@ -119,3 +119,15 @@ msgstr "Возникла ошибка в updateCommon."
|
|||
|
||||
msgid "Empty"
|
||||
msgstr "Пусто"
|
||||
|
||||
msgid "Invalid symlink type"
|
||||
msgstr "Неверный тип символической ссылки."
|
||||
|
||||
msgid "Bad symlink"
|
||||
msgstr "Плохая символическая ссылка."
|
||||
|
||||
msgid "Target '%s' not found"
|
||||
msgstr "Target '%s' отсутствует."
|
||||
|
||||
msgid "Link '%s' already exists"
|
||||
msgstr "Link '%s' уже существует."
|
||||
|
|
|
@ -134,3 +134,9 @@ msgstr "Если ДА, то новые сообщения пользовател
|
|||
|
||||
msgid "<span></span>"
|
||||
msgstr "<span></span>"
|
||||
|
||||
msgid "Friendly name label"
|
||||
msgstr "Имя для URL"
|
||||
|
||||
msgid "Friendly name help"
|
||||
msgstr "Строковый идентификатор на основе которого будет сформирован адрес указывающий на данный раздел. Может состоять из латинских букв, цифр, знаков подчеркивания и дефисов."
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
<div class="f-actions-links">
|
||||
<small>{!! __('ACTIONS') !!}</small>
|
||||
<small>|</small>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-create-topic" title="{{ __('Post topic') }}" href="{{ $p->model->linkCreateTopic }}"><span>{!! __('Post topic') !!}</span></a></span>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-create-topic" title="{{ __('Post topic') }}" href="{{ $p->model->linkCreateTopic }}" rel="nofollow"><span>{!! __('Post topic') !!}</span></a></span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -205,7 +205,7 @@
|
|||
@endif
|
||||
@if ($p->model->canCreateTopic)
|
||||
<small>|</small>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-create-topic" title="{{ __('Post topic') }}" href="{{ $p->model->linkCreateTopic }}"><span>{!! __('Post topic') !!}</span></a></span>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-create-topic" title="{{ __('Post topic') }}" href="{{ $p->model->linkCreateTopic }}" rel="nofollow"><span>{!! __('Post topic') !!}</span></a></span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<aside id="fork-debug">
|
||||
<!-- PRE inStart -->
|
||||
<p class="f-sim-header">{!! __('Debug table') !!}</p>
|
||||
<p id="id-fdebugtime">[ {!! __(['Generated in %1$s, %2$s queries', num(\microtime(true) - $p->start, 3), $p->numQueries]) !!} - {!! __(['Memory %1$s, Peak %2$s', size(\memory_get_usage()), size(\memory_get_peak_usage())]) !!} ]</p>
|
||||
<p id="id-fdebugtime">t = {{ num(\microtime(true) - $p->start, 3) }} : q = {{ $p->numQueries}} : m = {{ size(\memory_get_usage()) }} / {{ size(\memory_get_peak_usage()) }}</p>
|
||||
@if ($p->queries)
|
||||
<table id="fork-dgtable">
|
||||
<thead id="fork-dgthead">
|
||||
|
|
11
app/templates/_default/sitemap.forkbb.php
Normal file
11
app/templates/_default/sitemap.forkbb.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
@foreach ($p->sitemap as $loc => $lastmod)
|
||||
<url>
|
||||
<loc>{{ $loc }}</loc>
|
||||
@if ($lastmod)
|
||||
<lastmod>{{ \gmdate('c', $lastmod) }}</lastmod>
|
||||
@endif
|
||||
</url>
|
||||
@endforeach
|
||||
</urlset>
|
11
app/templates/_default/sitemap_index.forkbb.php
Normal file
11
app/templates/_default/sitemap_index.forkbb.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
@foreach ($p->sitemap as $loc => $lastmod)
|
||||
<sitemap>
|
||||
<loc>{{ $loc }}</loc>
|
||||
@if ($lastmod)
|
||||
<lastmod>{{ \gmdate('c', $lastmod) }}</lastmod>
|
||||
@endif
|
||||
</sitemap>
|
||||
@endforeach
|
||||
</sitemapindex>
|
|
@ -42,7 +42,7 @@
|
|||
@endif
|
||||
@if ($p->model->canReply)
|
||||
<small>|</small>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-post-reply" title="{{ __('Post reply') }}" href="{{ $p->model->linkReply }}"><span>{!! __('Post reply') !!}</span></a></span>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-post-reply" title="{{ __('Post reply') }}" href="{{ $p->model->linkReply }}" rel="nofollow"><span>{!! __('Post reply') !!}</span></a></span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
@ -164,7 +164,7 @@
|
|||
@endif
|
||||
@if ($post->canQuote)
|
||||
<small>-</small>
|
||||
<a class="f-btn f-postquote" title="{{ __('Quote') }}" href="{{ $post->linkQuote }}"><span>{!! __('Quote') !!}</span></a>
|
||||
<a class="f-btn f-postquote" title="{{ __('Quote') }}" href="{{ $post->linkQuote }}" rel="nofollow"><span>{!! __('Quote') !!}</span></a>
|
||||
@endif
|
||||
</aside>
|
||||
@endif
|
||||
|
@ -191,7 +191,7 @@
|
|||
@endif
|
||||
@if ($p->model->canReply)
|
||||
<small>|</small>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-post-reply" title="{{ __('Post reply') }}" href="{{ $p->model->linkReply }}"><span>{!! __('Post reply') !!}</span></a></span>
|
||||
<span class="f-act-span"><a class="f-btn f-btn-post-reply" title="{{ __('Post reply') }}" href="{{ $p->model->linkReply }}" rel="nofollow"><span>{!! __('Post reply') !!}</span></a></span>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
#
|
||||
AddDefaultCharset UTF-8
|
||||
|
||||
#Options +FollowSymLinks # For extensions with symlinks
|
||||
##Options -FollowSymLinks +SymLinksIfOwnerMatch # or this (more security(?), more checks(!!!))
|
||||
|
||||
<IfModule mod_autoindex.c>
|
||||
Options -Indexes
|
||||
</IfModule>
|
||||
|
|
|
@ -10,17 +10,6 @@
|
|||
top: 1rem;
|
||||
}
|
||||
|
||||
#fork-a-menu .f-menu-a.active {
|
||||
background-color: var(--bg-active);
|
||||
color: var(--c-active);
|
||||
}
|
||||
|
||||
#fork-a-menu .f-menu-a:hover,
|
||||
#fork-a-menu .f-menu-a:focus {
|
||||
background-color: var(--c-a-and-btn);
|
||||
color: var(--c-nav-focus);
|
||||
}
|
||||
|
||||
#fork-a-menu #id-an-label {
|
||||
position: absolute;
|
||||
top: -2.875rem;
|
||||
|
@ -1338,6 +1327,6 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
#forka .f-fbtn[data-name="uninstall"]:not(.origin) {
|
||||
#forka .f-fbtn[name="uninstall"]:not(.origin) {
|
||||
color: red;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
--bg-fh1: hsl(220, 5%, 12%);
|
||||
--br-fh1: hsl(0, 0%, 80%);
|
||||
--c-icon-sub: hsl(0, 0%, 70%);
|
||||
--c-icon-new: hsl(50, 100%, 50%);
|
||||
--c-focus-v: hsl(30, 100%, 50%);
|
||||
--c-a-focus: hsl(50, 100%, 40%);
|
||||
--c-focus: hsl(220, 5%, 12%);
|
||||
--bg-focus: hsl(50, 100%, 50%);
|
||||
--bg-post-h: hsl(0, 20%, 26%);
|
||||
|
@ -34,6 +37,7 @@
|
|||
--bg-poll-res: hsl(50, 100%, 50%);
|
||||
--c-sel: hsl(0, 0%, 0%);
|
||||
--bg-sel: hsl(0, 0%, 60%);
|
||||
--c-highlighted: hsl(39, 100%, 50%);
|
||||
}
|
||||
|
||||
html,
|
||||
|
@ -205,15 +209,16 @@ blockquote cite {
|
|||
margin: -0.625rem -0.625rem 0.625rem -0.625rem;
|
||||
}
|
||||
|
||||
a {
|
||||
a, span.f-bb-hashtag {
|
||||
color: var(--c-a-and-btn);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: var(--c-focus);
|
||||
background-color: var(--bg-focus);
|
||||
a:hover, a:focus, span.f-bb-hashtag:hover, span.f-bb-hashtag:focus {
|
||||
color: var(--c-a-focus);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
|
@ -285,7 +290,6 @@ div.f-bb-s-body {
|
|||
}
|
||||
|
||||
span.f-bb-hashtag {
|
||||
color: var(--c-a-and-btn);
|
||||
border-bottom-style: dashed;
|
||||
border-bottom-width: 0.0625rem;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
--br-fh1: hsl(0, 0%, 80%);
|
||||
--c-icon-sub: hsl(0, 0%, 70%);
|
||||
--c-icon-new: hsl(50, 100%, 50%);
|
||||
--c-focus-v: hsl(30, 100%, 50%);
|
||||
--c-a-focus: hsl(50, 100%, 40%);
|
||||
--c-focus: hsl(220, 5%, 12%);
|
||||
--bg-focus: hsl(50, 100%, 50%);
|
||||
--bg-post-h: hsl(0, 20%, 26%);
|
||||
|
@ -259,7 +261,7 @@ body,
|
|||
}
|
||||
|
||||
#fork select {
|
||||
background-color: var(--bg-focus);
|
||||
background-color: var(--bg-fprimary);
|
||||
}
|
||||
|
||||
#fork select:not([multiple]) option {
|
||||
|
@ -329,7 +331,7 @@ body,
|
|||
color: var(--c-a-and-btn);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition-duration: 1s;
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
#fork .f-btn {
|
||||
|
@ -352,18 +354,31 @@ body,
|
|||
}
|
||||
|
||||
#fork a:hover,
|
||||
#fork a:focus,
|
||||
#fork .f-btn:hover,
|
||||
#fork .f-btn:focus {
|
||||
color: var(--c-focus);
|
||||
background-color: var(--bg-focus);
|
||||
#fork a:focus {
|
||||
color: var(--c-a-focus);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#fork a:active,
|
||||
#fork a:focus-visible,
|
||||
#fork .f-btn:focus-visible {
|
||||
outline: 0.25rem double var(--c-focus-v);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#fork .f-btn:hover,
|
||||
#fork .f-btn:focus,
|
||||
#fork a.f-page:hover,
|
||||
#fork a.f-page:focus {
|
||||
color: var(--c-focus);
|
||||
background-color: var(--bg-focus);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*#fork a:active,
|
||||
#fork .f-btn:active {
|
||||
color: var(--c-active);
|
||||
background-color: var(--bg-active);
|
||||
}
|
||||
}*/
|
||||
|
||||
#fork .f-inline > dt,
|
||||
#fork .f-inline > dd {
|
||||
|
@ -407,7 +422,6 @@ body,
|
|||
background-color: var(--bg-like-nav);
|
||||
}
|
||||
|
||||
|
||||
/********/
|
||||
/* Меню */
|
||||
/********/
|
||||
|
@ -473,6 +487,18 @@ body,
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#fork .f-menu-a.active {
|
||||
background-color: var(--bg-active);
|
||||
color: var(--c-active);
|
||||
}
|
||||
|
||||
#fork .f-menu-a:hover,
|
||||
#fork .f-menu-a:focus {
|
||||
background-color: var(--c-a-and-btn);
|
||||
color: var(--c-nav-focus);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 50rem) {
|
||||
#fork .f-menu-checkbox {
|
||||
display: none;
|
||||
|
@ -514,17 +540,6 @@ body,
|
|||
padding: 0.3125rem 0.625rem;
|
||||
}
|
||||
|
||||
#fork-nav .f-menu-a.active {
|
||||
background-color: var(--bg-active);
|
||||
color: var(--c-active);
|
||||
}
|
||||
|
||||
#fork-nav .f-menu-a:hover,
|
||||
#fork-nav .f-menu-a:focus {
|
||||
background-color: var(--c-a-and-btn);
|
||||
color: var(--c-nav-focus);
|
||||
}
|
||||
|
||||
#fork-nav .f-menu-user-items {
|
||||
background-color: var(--bg-nav);
|
||||
display: flex;
|
||||
|
@ -955,7 +970,6 @@ body,
|
|||
|
||||
#fork-debug #id-fdebugtime {
|
||||
padding: 0.3125rem 0.625rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#fork-debug #fork-dgtable {
|
||||
|
@ -1283,9 +1297,9 @@ body,
|
|||
width: calc(100% - 2rem);
|
||||
}
|
||||
|
||||
#fork .f-ftlist .f-ftname {
|
||||
/*#fork .f-ftlist .f-ftname {
|
||||
max-width: 100%;
|
||||
}
|
||||
}*/
|
||||
|
||||
#fork .f-ftlist .f-cstats {
|
||||
display: flex;
|
||||
|
@ -2727,17 +2741,6 @@ body,
|
|||
top: 1rem;
|
||||
}
|
||||
|
||||
#fork-pm-menu a.f-menu-a.active {
|
||||
background-color: var(--bg-active);
|
||||
color: var(--c-active);
|
||||
}
|
||||
|
||||
#fork-pm-menu a.f-menu-a:hover,
|
||||
#fork-pm-menu a.f-menu-a:focus {
|
||||
background-color: var(--c-a-and-btn);
|
||||
color: var(--c-nav-focus);
|
||||
}
|
||||
|
||||
#fork-pm-menu #id-pmn-label {
|
||||
position: absolute;
|
||||
top: -2.875rem;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# ForkBB rev.71 Alpha Readme
|
||||
# ForkBB rev.73 Alpha Readme
|
||||
|
||||
## About
|
||||
|
||||
|
@ -13,7 +13,7 @@ ForkBB is a free and open source forum software. The project is based on [FluxBB
|
|||
|
||||
## Install
|
||||
|
||||
Topic [Установка (Installation)](https://forkbb.ru/topic/28/%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0%20%28Installation%29)
|
||||
Topic [Установка (Installation)](https://forkbb.ru/topic/28/ustanovka-installation)
|
||||
|
||||
### For Apache:
|
||||
|
||||
|
@ -43,6 +43,8 @@ Disallow: /reg
|
|||
Disallow: /search
|
||||
Disallow: /post
|
||||
Disallow: /forum/scroll
|
||||
Disallow: /forum/*/new/topic
|
||||
Disallow: /topic/*/new/reply
|
||||
Disallow: /userlist/*/DESC/
|
||||
Disallow: /userlist/*/ASC/
|
||||
```
|
||||
|
|
Loading…
Add table
Reference in a new issue