Compare commits
82 commits
Extensions
...
master
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 | ||
![]() |
d5eec724d6 | ||
![]() |
acaad2db29 | ||
![]() |
8e3c74367e | ||
![]() |
e6d66f7e0a | ||
![]() |
4f2c637134 | ||
![]() |
c5f2aa0a97 | ||
![]() |
5ac2b20ff2 | ||
![]() |
414e3d9717 | ||
![]() |
25691fa3af | ||
![]() |
c0bb06dc13 | ||
![]() |
4bde2ad136 | ||
![]() |
1d917f0151 | ||
![]() |
d64b016637 | ||
![]() |
1c7d91b643 | ||
![]() |
da79516766 | ||
![]() |
327c5cfb1a | ||
![]() |
b57a66f0cf | ||
![]() |
3891d8fced | ||
![]() |
0cf80df852 |
78 changed files with 2183 additions and 329 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,18 +977,17 @@ class Files
|
|||
*/
|
||||
public function filterName(string $name): string
|
||||
{
|
||||
$name = \transliterator_transliterate(
|
||||
"Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; Lower();",
|
||||
$name
|
||||
);
|
||||
|
||||
$name = \trim(\preg_replace(['%[^\w-]+%', '%_+%'], ['-', '_'], $name), '-_');
|
||||
|
||||
if (! isset($name[0])) {
|
||||
$name = (string) \time();
|
||||
if (null === $this->transl) {
|
||||
$this->transl = Transliterator::create('Any-Latin;Latin-ASCII;Lower();') ?? false;
|
||||
}
|
||||
|
||||
return $name;
|
||||
if ($this->transl instanceof Transliterator) {
|
||||
$name = $this->transl->transliterate($name);
|
||||
}
|
||||
|
||||
$name = \trim(\preg_replace(['%[^\w]+%', '%_+%'], ['-', '_'], $name), '-_');
|
||||
|
||||
return isset($name[0]) ? $name : (string) \time();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1095,32 +1100,16 @@ class Files
|
|||
protected function uploadFile(array $file, bool $isUploaded = true): ?File
|
||||
{
|
||||
if (\UPLOAD_ERR_OK !== $file['error']) {
|
||||
switch ($file['error']) {
|
||||
case \UPLOAD_ERR_INI_SIZE:
|
||||
$this->error = 'The uploaded file exceeds the upload_max_filesize';
|
||||
break;
|
||||
case \UPLOAD_ERR_FORM_SIZE:
|
||||
$this->error = 'The uploaded file exceeds the MAX_FILE_SIZE';
|
||||
break;
|
||||
case \UPLOAD_ERR_PARTIAL:
|
||||
$this->error = 'The uploaded file was only partially uploaded';
|
||||
break;
|
||||
case \UPLOAD_ERR_NO_FILE:
|
||||
$this->error = 'No file was uploaded';
|
||||
break;
|
||||
case \UPLOAD_ERR_NO_TMP_DIR:
|
||||
$this->error = 'Missing a temporary folder';
|
||||
break;
|
||||
case \UPLOAD_ERR_CANT_WRITE:
|
||||
$this->error = 'Failed to write file to disk';
|
||||
break;
|
||||
case \UPLOAD_ERR_EXTENSION:
|
||||
$this->error = 'A PHP extension stopped the file upload';
|
||||
break;
|
||||
default:
|
||||
$this->error = 'Unknown upload error';
|
||||
break;
|
||||
}
|
||||
$this->error = match ($file['error']) {
|
||||
\UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize',
|
||||
\UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE',
|
||||
\UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded',
|
||||
\UPLOAD_ERR_NO_FILE => 'No file was uploaded',
|
||||
\UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
|
||||
\UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
|
||||
\UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload',
|
||||
default => 'Unknown upload error',
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -1148,16 +1137,22 @@ class Files
|
|||
$ext = \mb_strtolower(\substr($file['name'], $pos + 1), 'UTF-8');
|
||||
}
|
||||
|
||||
$imageExt = $this->imageExt($file['tmp_name']);
|
||||
$mimeType = $this->mimeType($file['tmp_name']);
|
||||
|
||||
if (\is_string($imageExt)) {
|
||||
if (! isset($this->mimeToExt[$mimeType])) {
|
||||
$this->error = "Unknown mime type of the file: {$mimeType}";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($this->imageType[$mimeType])) {
|
||||
if ($file['size'] > $this->maxImgSize) {
|
||||
$this->error = 'The image too large';
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$ext = $imageExt;
|
||||
$ext = $this->imageType[$mimeType];
|
||||
$className = Image::class;
|
||||
} else {
|
||||
if ($file['size'] > $this->maxFileSize) {
|
||||
|
@ -1169,14 +1164,6 @@ class Files
|
|||
$className = File::class;
|
||||
}
|
||||
|
||||
$mimeType = $this->mimeType($file['tmp_name']);
|
||||
|
||||
if (! isset($this->mimeToExt[$mimeType])) {
|
||||
$this->error = "Unknown mime type of the file: {$mimeType}";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$realExt = $this->mimeToExt[$mimeType];
|
||||
|
||||
if (false === \strpos("/{$realExt}/", "/{$ext}/")) {
|
||||
|
@ -1201,21 +1188,32 @@ class Files
|
|||
}
|
||||
|
||||
/**
|
||||
* Получает файл по внешней ссылке
|
||||
* Получает файл по внешней ссылке или из строки data:...;base64,...
|
||||
*/
|
||||
public function uploadFromLink(string $url): ?File
|
||||
{
|
||||
$cmpn = \parse_url($url);
|
||||
if (\preg_match('%^data:(.*?);base64,%', $url, $matches)) {
|
||||
$name = '';
|
||||
$type = $matches[1];
|
||||
$offset = \strlen($matches[0]);
|
||||
} else {
|
||||
$cmpn = \parse_url($url);
|
||||
|
||||
if (
|
||||
! isset($cmpn['scheme'], $cmpn['host'], $cmpn['path'])
|
||||
|| ! \in_array($cmpn['scheme'], ['https', 'http'], true)
|
||||
) {
|
||||
$this->error = 'Bad url';
|
||||
if (
|
||||
! isset($cmpn['scheme'], $cmpn['host'], $cmpn['path'])
|
||||
|| ! \in_array($cmpn['scheme'], ['https', 'http'], true)
|
||||
) {
|
||||
$this->error = 'Bad url';
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = \basename($cmpn['path']) ?: '';
|
||||
$type = '';
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
|
||||
$tmpName = $this->c->DIR_CACHE . '/' . $this->c->Secury->randomPass(32) . '.tmp';
|
||||
|
||||
$this->addTmpFile($tmpName);
|
||||
|
@ -1230,7 +1228,21 @@ class Files
|
|||
|
||||
$result = null;
|
||||
|
||||
if (\extension_loaded('curl')) {
|
||||
if ($offset > 0) {
|
||||
$content = \base64_decode(\substr($url, $offset), true);
|
||||
|
||||
if (false === $content) {
|
||||
$this->error = 'Bad base64';
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = (bool) @\fwrite($tmpFile, $content);
|
||||
|
||||
if (false === $result) {
|
||||
$this->error = "Failed fwrite() to temp file";
|
||||
}
|
||||
} elseif (\extension_loaded('curl')) {
|
||||
$result = $this->curlAction($url, $tmpFile);
|
||||
} elseif (\filter_var(\ini_get('allow_url_fopen'), \FILTER_VALIDATE_BOOL)) {
|
||||
$result = $this->streamAction($url, $tmpFile);
|
||||
|
@ -1242,8 +1254,8 @@ class Files
|
|||
return $this->uploadFile(
|
||||
[
|
||||
'tmp_name' => $tmpName,
|
||||
'name' => \basename($cmpn['path']) ?: '',
|
||||
'type' => '',
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'error' => \UPLOAD_ERR_OK,
|
||||
'size' => \filesize($tmpName),
|
||||
],
|
||||
|
@ -1259,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 = [
|
||||
|
@ -1279,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 : [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -302,4 +319,39 @@ class Func
|
|||
return $timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует строку в соотвествии с правилами FRIENDLY_URL
|
||||
*/
|
||||
public function friendly(string $str): string
|
||||
{
|
||||
if (! empty($this->fUrl['translit'])) {
|
||||
if (! empty($this->fUrl['file'])) {
|
||||
$this->translArray ??= include "{$this->c->DIR_CONFIG}/{$this->fUrl['file']}";
|
||||
|
||||
$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 (true === $this->fUrl['lowercase']) {
|
||||
$str = \mb_strtolower($str, 'UTF-8');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -505,4 +505,20 @@ EOD;
|
|||
{
|
||||
return "<?php break; ?>";
|
||||
}
|
||||
|
||||
/**
|
||||
* @php
|
||||
*/
|
||||
protected function compilePhp(): string
|
||||
{
|
||||
return "<?php";
|
||||
}
|
||||
|
||||
/**
|
||||
* @endphp
|
||||
*/
|
||||
protected function compileEndphp(): string
|
||||
{
|
||||
return " ?>";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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->forum_name,
|
||||
'name' => $this->friendly,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -261,7 +269,7 @@ class Forum extends DataModel
|
|||
'User',
|
||||
[
|
||||
'id' => $id,
|
||||
'name' => $cur,
|
||||
'name' => $this->c->Func->friendly($cur),
|
||||
]
|
||||
)
|
||||
: null,
|
||||
|
@ -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->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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ class Info extends Method
|
|||
'User',
|
||||
[
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
'name' => $this->c->Func->friendly($name),
|
||||
]
|
||||
)
|
||||
: null,
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -175,7 +175,7 @@ class Logs extends Admin
|
|||
$data = $this->c->LogViewer->parse($path);
|
||||
|
||||
foreach ($data as &$cur) {
|
||||
$cur['context'] = \print_r($cur['context'], true);
|
||||
$cur['context'] = \preg_replace('%^\s*Array\s*\(\n(.+)\n\)\s*$%s', '$1', \print_r($cur['context'], true));
|
||||
}
|
||||
|
||||
unset($cur);
|
||||
|
|
|
@ -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';
|
||||
|
@ -979,4 +979,91 @@ class Update extends Admin
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* rev.70 to rev.71
|
||||
*/
|
||||
protected function stageNumber70(array $args): ?int
|
||||
{
|
||||
$coreConfig = new CoreConfig($this->configFile);
|
||||
|
||||
$coreConfig->add(
|
||||
'FRIENDLY_URL',
|
||||
[
|
||||
'lowercase' => 'false',
|
||||
'translit' => '\'\'',
|
||||
'WtoHyphen' => 'false',
|
||||
],
|
||||
'TIME_FORMATS'
|
||||
);
|
||||
|
||||
$coreConfig->save();
|
||||
|
||||
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' => $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}",
|
||||
]
|
||||
|
|
|
@ -31,7 +31,7 @@ class Index extends Page
|
|||
'User',
|
||||
[
|
||||
'id' => $this->c->stats->userLast['id'],
|
||||
'name' => $this->c->stats->userLast['username'],
|
||||
'name' => $this->c->Func->friendly($this->c->stats->userLast['username']),
|
||||
]
|
||||
)
|
||||
: null,
|
||||
|
|
|
@ -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',
|
||||
]);
|
||||
|
||||
|
|
|
@ -85,12 +85,7 @@ class Post extends Page
|
|||
}
|
||||
|
||||
$this->nameTpl = 'post';
|
||||
$this->canonical = $this->c->Router->link(
|
||||
'NewTopic',
|
||||
[
|
||||
'id' => $forum->id,
|
||||
]
|
||||
);
|
||||
$this->canonical = $forum->linkCreateTopic;
|
||||
$this->robots = 'noindex';
|
||||
$this->formTitle = 'Post new topic';
|
||||
$this->crumbs = $this->crumbs($this->formTitle, $forum);
|
||||
|
@ -157,12 +152,7 @@ class Post extends Page
|
|||
}
|
||||
|
||||
$this->nameTpl = 'post';
|
||||
$this->canonical = $this->c->Router->link(
|
||||
'NewReply',
|
||||
[
|
||||
'id' => $topic->id,
|
||||
]
|
||||
);
|
||||
$this->canonical = $topic->linkReply;
|
||||
$this->robots = 'noindex';
|
||||
$this->formTitle = 'Post a reply';
|
||||
$this->crumbs = $this->crumbs($this->formTitle, $topic);
|
||||
|
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ForkBB\Models\Pages;
|
||||
|
||||
use ForkBB\Core\Image;
|
||||
use ForkBB\Core\Validator;
|
||||
use ForkBB\Models\Model;
|
||||
use function \ForkBB\__;
|
||||
|
@ -103,10 +104,7 @@ trait PostValidatorTrait
|
|||
{
|
||||
$this->c->Lang->load('validator');
|
||||
|
||||
// обработка вложений + хак с добавление вложений в сообщение на лету
|
||||
if (\is_string($attMessage = $this->attachmentsProc($marker, $args))) {
|
||||
$_POST['message'] .= $attMessage;
|
||||
}
|
||||
$this->attachmentsProc($marker, $args);
|
||||
|
||||
$notPM = $this->fIndex !== self::FI_PM;
|
||||
|
||||
|
@ -297,55 +295,83 @@ trait PostValidatorTrait
|
|||
/**
|
||||
* Обрабатывает загруженные файлы
|
||||
*/
|
||||
protected function attachmentsProc(string $marker, array $args): ?string
|
||||
protected function attachmentsProc(string $marker, array $args): void
|
||||
{
|
||||
if (! $this->userRules->useUpload) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
$v = $this->c->Validator->reset()
|
||||
->addValidators([
|
||||
'check_attach' => [$this, 'vCheckAttach'],
|
||||
'check_attach' => [$this, 'vCheckAttach'],
|
||||
])->addRules([
|
||||
'token' => 'token:' . $marker,
|
||||
'attachments' => "file:multiple|max:{$this->user->g_up_size_kb}|check_attach",
|
||||
'token' => 'token:' . $marker,
|
||||
'message' => 'string:trim',
|
||||
'attachments' => "file:multiple|max:{$this->user->g_up_size_kb}|check_attach",
|
||||
])->addAliases([
|
||||
'attachments' => 'Attachments',
|
||||
'attachments' => 'Attachments',
|
||||
])->addArguments([
|
||||
'token' => $args,
|
||||
'token' => $args,
|
||||
])->addMessages([
|
||||
]);
|
||||
|
||||
if (! $v->validation($_FILES + $_POST)) {
|
||||
$this->fIswev = $v->getErrors();
|
||||
|
||||
return null;
|
||||
} elseif (! \is_array($v->attachments)) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
$result = "\n";
|
||||
$calc = false;
|
||||
$calc = false;
|
||||
|
||||
foreach ($v->attachments as $file) {
|
||||
$data = $this->c->attachments->addFile($file);
|
||||
// костыль с конвертацией картинок из base64 в файлы
|
||||
$_POST['message'] = \preg_replace_callback(
|
||||
'%\[img\](data:[\w/.+-]*+;base64,[a-zA-Z0-9/+=]++)\[/img\]%',
|
||||
function ($matches) use ($calc) {
|
||||
$file = $this->c->Files->uploadFromLink($matches[1]);
|
||||
|
||||
if (\is_array($data)) {
|
||||
$name = $file->name();
|
||||
$calc = true;
|
||||
|
||||
if ($data['image']) {
|
||||
$result .= "[img]{$data['url']}[/img]\n"; // ={$name}
|
||||
} else {
|
||||
$result .= "[url={$data['url']}]{$name}[/url]\n";
|
||||
if (! $file instanceof Image) {
|
||||
return $this->c->Files->error() ?? 'Bad image';
|
||||
}
|
||||
|
||||
$data = $this->c->attachments->addFile($file);
|
||||
|
||||
if (\is_array($data)) {
|
||||
$calc = true;
|
||||
|
||||
return "[img]{$data['url']}[/img]";
|
||||
} else {
|
||||
return 'Bad file';
|
||||
}
|
||||
},
|
||||
(string) $v->message
|
||||
);
|
||||
|
||||
if (\is_array($v->attachments)) {
|
||||
$result = '';
|
||||
|
||||
foreach ($v->attachments as $file) {
|
||||
$data = $this->c->attachments->addFile($file);
|
||||
|
||||
if (\is_array($data)) {
|
||||
$name = $file->name();
|
||||
$calc = true;
|
||||
|
||||
if ($data['image']) {
|
||||
$result .= "\n[img]{$data['url']}[/img]"; // ={$name}
|
||||
} else {
|
||||
$result .= "\n[url={$data['url']}]{$name}[/url]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// костыль с добавление вложений в сообщение на лету
|
||||
if ('' !== $result) {
|
||||
$_POST['message'] .= $result;
|
||||
}
|
||||
}
|
||||
|
||||
if ($calc) {
|
||||
$this->c->attachments->recalculate($this->user);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -140,8 +140,8 @@ class Topic extends Page
|
|||
'Topic',
|
||||
[
|
||||
'id' => $topic->id,
|
||||
'name' => $topic->name,
|
||||
'page' => $topic->page
|
||||
'name' => $this->c->Func->friendly($topic->name),
|
||||
'page' => $topic->page,
|
||||
]
|
||||
);
|
||||
$this->model = $topic;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -101,7 +101,7 @@ class Topic extends DataModel
|
|||
'Topic',
|
||||
[
|
||||
'id' => $this->moved_to ?: $this->id,
|
||||
'name' => $this->name,
|
||||
'name' => $this->c->Func->friendly($this->name),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ class Topic extends DataModel
|
|||
'Topic',
|
||||
[
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'name' => $this->c->Func->friendly($this->name),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ class User extends DataModel
|
|||
'User',
|
||||
[
|
||||
'id' => $this->id,
|
||||
'name' => $this->username,
|
||||
'name' => $this->c->Func->friendly($this->username),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ if (
|
|||
$c->BASE_URL = \str_replace('https://', 'http://', $c->BASE_URL);
|
||||
}
|
||||
|
||||
$c->FORK_REVISION = 70;
|
||||
$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',
|
||||
|
|
|
@ -42,7 +42,7 @@ return [
|
|||
],
|
||||
// Разрешенные атрибуты тегов / Allowed tag attributes
|
||||
'cfgAllowTagParams' => [
|
||||
['a', ['class', 'title', 'href']],
|
||||
['a', ['class', 'title', 'href', 'rel']],
|
||||
['abbr', ['class']],
|
||||
['address', ['class']],
|
||||
['article', ['class']],
|
||||
|
@ -110,7 +110,7 @@ return [
|
|||
// [тег, атрибут, значение, перезапись существующего атрибута]
|
||||
// [tag, attribute, value, overwrite existing attribute]
|
||||
'cfgSetTagParamDefault' => [
|
||||
['a', 'rel', 'ugc', true],
|
||||
// ['a', 'rel', 'ugc', false],
|
||||
['img', 'alt', 'image', false],
|
||||
['img', 'loading', 'lazy', true],
|
||||
],
|
||||
|
|
|
@ -74,6 +74,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',
|
||||
],
|
||||
|
||||
'shared' => [
|
||||
'%DIR_ROOT%' => \realpath(__DIR__ . '/../..'),
|
||||
|
@ -366,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
|
@ -88,4 +88,4 @@ msgid "Maintenance only"
|
|||
msgstr "Available only in maintenance mode."
|
||||
|
||||
msgid "Extensions"
|
||||
msgstr "Расширения"
|
||||
msgstr "Extensions"
|
||||
|
|
|
@ -116,3 +116,18 @@ msgstr "Template file '%s' not found."
|
|||
|
||||
msgid "An error occurred in updateCommon"
|
||||
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."
|
||||
|
|
|
@ -116,3 +116,18 @@ msgstr "Файл шаблона '%s' не найден."
|
|||
|
||||
msgid "An error occurred in updateCommon"
|
||||
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 "Строковый идентификатор на основе которого будет сформирован адрес указывающий на данный раздел. Может состоять из латинских букв, цифр, знаков подчеркивания и дефисов."
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div>
|
||||
<fieldset>
|
||||
<ol>
|
||||
@foreach ($p->extensions as $ext)
|
||||
@forelse ($p->extensions as $ext)
|
||||
<li id="{{ $ext->id }}" class="f-extli f-ext-status{{ $ext->status }}">
|
||||
<details class="f-extdtl">
|
||||
<summary class="f-extsu">
|
||||
|
@ -35,7 +35,7 @@
|
|||
{!! __('Crash') !!}
|
||||
@break
|
||||
@endswitch
|
||||
/<span>
|
||||
/</span>
|
||||
</summary>
|
||||
<div class="f-extdata f-fdiv">
|
||||
<form class="f-form" method="post" action="{{ $p->actionLink }}">
|
||||
|
@ -85,6 +85,7 @@
|
|||
<legend class="f-fleg">{!! __('Authors') !!}</legend>
|
||||
@foreach ($ext->authors as $author)
|
||||
<dl>
|
||||
<dt class="f-extdt-empty"></dt>
|
||||
<dd class="f-extdd-author">
|
||||
<span>{{ $author['name'] }}</span>
|
||||
@if (! empty($author['email']) || ! empty($author['homepage']))
|
||||
|
@ -107,9 +108,10 @@
|
|||
</dl>
|
||||
@endforeach
|
||||
</fieldset>
|
||||
<fieldset calss="f-extfs-confirm">
|
||||
<fieldset class="f-extfs-confirm">
|
||||
<dl>
|
||||
<dd>
|
||||
<dt class="f-extdt-empty"></dt>
|
||||
<dd class="f-extdd-confirm">
|
||||
<label class="f-flblch"><input name="confirm" class="f-ychk" type="checkbox" value="1">{!! __('Confirm action') !!}</label>
|
||||
</dd>
|
||||
</dl>
|
||||
|
@ -140,7 +142,12 @@
|
|||
</div>
|
||||
</details>
|
||||
</li>
|
||||
@endforeach
|
||||
@empty
|
||||
<li>
|
||||
@php $iswev = [FORK_MESS_INFO => ['Empty']]; @endphp
|
||||
@include ('layouts/iswev')
|
||||
</li>
|
||||
@endforelse
|
||||
</ol>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
@ -38,7 +38,8 @@
|
|||
<section id="fork-uploads-files" class="f-admin">
|
||||
<h2>{!! __('File list head') !!}</h2>
|
||||
<div class="f-fdiv">
|
||||
@if (null !== $p->badPage && $iswev = [FORK_MESS_ERR => [['Page %s missing', $p->badPage]]])
|
||||
@if (null !== $p->badPage)
|
||||
@php $iswev = [FORK_MESS_ERR => [['Page %s missing', $p->badPage]]]; @endphp
|
||||
@include ('layouts/iswev')
|
||||
@elseif ($form = $p->formFileList)
|
||||
@include ('layouts/form')
|
||||
|
|
|
@ -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>
|
||||
|
@ -87,7 +87,8 @@
|
|||
<div class="f-hcell f-clast">{!! __('Last post') !!}</div>
|
||||
</li>
|
||||
@foreach ($p->topics as $id => $topic)
|
||||
@if (empty($topic->id) && $iswev = [FORK_MESS_ERR => [['Topic %s was not found in the database', $id]]])
|
||||
@if (empty($topic->id))
|
||||
@php $iswev = [FORK_MESS_ERR => [['Topic %s was not found in the database', $id]]]; @endphp
|
||||
<li id="topic-{{ $id }}" class="f-row">
|
||||
@include ('layouts/iswev')
|
||||
</li>
|
||||
|
@ -204,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">
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<dl id="id-dl-{{ $cur['id'] or $key }}" @if ($cur['class']) class="f-field-{{ \implode(' f-field-', $cur['class']) }}" @endif>
|
||||
<dt>
|
||||
@if ($cur['caption'])
|
||||
<label class="f-ycaption @if ($cur['required']) f-req @endif" @if (false === \strpos('.radio.yield.str.btn.link.label.', ".{$cur['type']}.")) for="id-{{ $key }}" @endif>{!! __($cur['caption']) !!}</label>
|
||||
<label class="f-ycaption @if ($cur['required']) f-req @endif" @if (false === \strpos('.radio.yield.str.btn.link.label.include.', ".{$cur['type']}.")) for="id-{{ $key }}" @endif>{!! __($cur['caption']) !!}</label>
|
||||
@endif
|
||||
</dt>
|
||||
<dd>
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
@extends ('layouts/pm')
|
||||
<section id="fork-pm-bl" class="f-pm f-pm-bl-list @empty ($p->blockList) f-pm-bl-empty @endempty">
|
||||
<h2>{!! __('Blocked users title') !!}</h2>
|
||||
@if (empty($p->blockList) && $iswev = [FORK_MESS_INFO => ['No blocked users']])
|
||||
@include ('layouts/iswev')
|
||||
@empty ($p->blockList)
|
||||
@php $iswev = [FORK_MESS_INFO => ['No blocked users']]; @endphp
|
||||
@include ('layouts/iswev')
|
||||
@else
|
||||
<div>
|
||||
<fieldset>
|
||||
<ol id="fork-pm-bl-ol">
|
||||
@foreach ($p->blockList as $user)
|
||||
@foreach ($p->blockList as $user)
|
||||
<li class="f-pm-bl-li">
|
||||
<a href="{{ $user->link }}">{{ $user->username }}</a>
|
||||
<a class="f-btn" href="{{ $user->linkPMUnblock }}" title="{{ __(['Unblock user %s', $user->username]) }}">{!! __('Unblock') !!}</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</ol>
|
||||
</fieldset>
|
||||
</div>
|
||||
@endif
|
||||
@endempty
|
||||
</section>
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
<section id="fork-topic" class="f-pm f-pmtopic">
|
||||
<h2>{{ $p->model->name }}</h2>
|
||||
@foreach ($p->posts as $id => $post)
|
||||
@if (empty($post->id) && $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]])
|
||||
@if (empty($post->id))
|
||||
@php $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]]; @endphp
|
||||
@include ('layouts/iswev')
|
||||
@else
|
||||
<article id="p{{ $post->id }}" class="f-post @if (FORK_GEN_MAN == $post->user->gender) f-user-male @elseif (FORK_GEN_FEM == $post->user->gender) f-user-female @endif @if ($post->user->online) f-user-online @endif @if (1 === $post->postNumber) f-post-first @endif">
|
||||
|
|
|
@ -29,8 +29,9 @@
|
|||
@endif
|
||||
<section id="fork-forum" class="f-pm f-pmview @empty ($p->pmList) f-pm-empty @endempty">
|
||||
<h2>{!! __($p->title) !!}</h2>
|
||||
@if (empty($p->pmList) && $iswev = [FORK_MESS_INFO => ['Info zero']])
|
||||
@include ('layouts/iswev')
|
||||
@empty ($p->pmList)
|
||||
@php $iswev = [FORK_MESS_INFO => ['Info zero']]; @endphp
|
||||
@include ('layouts/iswev')
|
||||
@else
|
||||
<div class="f-ftlist">
|
||||
<ol class="f-table">
|
||||
|
@ -40,9 +41,10 @@
|
|||
<div class="f-hcell f-clast">{!! __('Last post') !!}</div>
|
||||
</li>
|
||||
@foreach ($p->pmList as $id => $topic)
|
||||
@if (empty($topic->id) && $iswev = [FORK_MESS_ERR => [['Dialogue %s was not found in the database', $id]]])
|
||||
@empty ($topic->id)
|
||||
@php $iswev = [FORK_MESS_ERR => [['Dialogue %s was not found in the database', $id]]]; @endphp
|
||||
<li id="ptopic-{{ $id }}" class="f-row">
|
||||
@include ('layouts/iswev')
|
||||
@include ('layouts/iswev')
|
||||
</li>
|
||||
@else
|
||||
<li id="ptopic-{{ $topic->id }}" class="f-row @if ($topic->hasNew) f-fnew @endif @if ($topic->closed) f-fclosed @endif">
|
||||
|
@ -85,11 +87,11 @@
|
|||
<span class="f-clposter">{!! __(['by %s', $topic->last_poster]) !!}</span>
|
||||
</div>
|
||||
</li>
|
||||
@endif
|
||||
@endempty
|
||||
@endforeach
|
||||
</ol>
|
||||
</div>
|
||||
@endif
|
||||
@endempty
|
||||
</section>
|
||||
@if ($p->pagination || $p->form)
|
||||
<div class="f-pm f-nav-links">
|
||||
|
|
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
|
||||
|
@ -54,7 +54,8 @@
|
|||
<section id="fork-topic" class="f-main">
|
||||
<h2>{!! __('Post list') !!}</h2>
|
||||
@foreach ($p->posts as $id => $post)
|
||||
@if (empty($post->id) && $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]])
|
||||
@empty ($post->id))
|
||||
@php $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]]; @endphp
|
||||
@include ('layouts/iswev')
|
||||
@else
|
||||
<article id="p{{ $post->id }}" class="f-post @if (FORK_GEN_MAN == $post->user->gender) f-user-male @elseif (FORK_GEN_FEM == $post->user->gender) f-user-female @endif @if ($post->user->online) f-user-online @endif @if (1 === $post->postNumber) f-post-first @endif">
|
||||
|
@ -163,13 +164,13 @@
|
|||
@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
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
@endempty
|
||||
@endforeach
|
||||
</section>
|
||||
<!-- PRE mainAfter -->
|
||||
|
@ -190,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
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
<section id="fork-topic-ins" class="f-main">
|
||||
<h2>{!! __('Post list') !!}</h2>
|
||||
@foreach ($p->posts as $id => $post)
|
||||
@if (empty($post->id) && $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]])
|
||||
@empty ($post->id))
|
||||
@php $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]]; @endphp
|
||||
@include ('layouts/iswev')
|
||||
@else
|
||||
<article id="p{{ $post->id }}" class="f-post f-post-search @if (FORK_GEN_MAN == $post->user->gender) f-user-male @elseif (FORK_GEN_FEM == $post->user->gender) f-user-female @endif @if ($post->user->online) f-user-online @endif">
|
||||
|
@ -98,7 +99,7 @@
|
|||
</aside>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
@endempty
|
||||
@endforeach
|
||||
</section>
|
||||
<!-- PRE mainAfter -->
|
||||
|
|
|
@ -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;
|
||||
|
@ -1297,7 +1286,12 @@
|
|||
padding: 0.3125rem;
|
||||
}
|
||||
|
||||
#fork-extsinfo .f-extdd-author {
|
||||
#fork-extsinfo .f-extdt-empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#fork-extsinfo .f-extdd-author,
|
||||
#fork-extsinfo .f-extdd-confirm {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -1333,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;
|
||||
|
|
10
readme.md
10
readme.md
|
@ -1,4 +1,4 @@
|
|||
# ForkBB rev.69 Alpha Readme
|
||||
# ForkBB rev.73 Alpha Readme
|
||||
|
||||
## About
|
||||
|
||||
|
@ -13,17 +13,17 @@ 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:
|
||||
|
||||
Apache must have **mod_rewrite** and **mod_headers** enabled. Also, the **AllowOverride** directive must be set to **All**.
|
||||
|
||||
Two options
|
||||
1. Document Root != **public** folder:
|
||||
1. Document Root != **public** folder (shared hosting):
|
||||
* Rename **.dist.htaccess** to **.htaccess**,
|
||||
* Rename **index.dist.php** to **index.php**.
|
||||
2. Document Root == **public** folder (recommended):
|
||||
2. Document Root == **public** folder (recommended if you have access to Apache configuration):
|
||||
* Rename public/**.dist.htaccess** to public/**.htaccess**,
|
||||
* Rename public/**index.dist.php** to public/**index.php**;
|
||||
|
||||
|
@ -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