Compare commits

..

82 commits

Author SHA1 Message Date
Visman
e88121236c Minor changes 2023-11-22 23:21:30 +07:00
Visman
13598f350e Use current Forums 2023-11-22 23:05:08 +07:00
Visman
46714487d4 Use the current Forums manager in the Forum model
Before this commit, the container->forums manager was used, which caused a logical error when deleting messages if there were hidden forums.
2023-11-22 13:51:52 +07:00
Visman
7530f5fee0 Models\Model.: Add setManager() method to pass the current Manager to the model 2023-11-22 13:48:33 +07:00
Visman
8ec4676535 Fix 2 for #29
:)
2023-11-22 00:40:16 +07:00
Visman
6917fc8e53 Fix for #29 2023-11-22 00:37:45 +07:00
Visman
f53b6965dc Change debug info 2023-11-20 14:45:59 +07:00
Visman
6687175940 Update sccontent.css 2023-11-20 12:42:11 +07:00
Visman
46df93ef9c Change the method for receiving all user messages
The previous method increased memory consumption as the number of user posts increased.
2023-11-19 23:38:20 +07:00
Visman
d091b7e30d Update style for links 2023-11-19 12:55:32 +07:00
Visman
db591e53fa Set :focus-visible only for a and .f-btn 2023-11-18 19:04:54 +07:00
Visman
766e79b8ed Close links to posts/topics of users for visitors identified as bots 2023-11-18 19:01:54 +07:00
Visman
af6fd98f09 Style: Add :focus-visible 2023-11-18 17:52:41 +07:00
Visman
f3976236ec Update style for links 2023-11-18 17:18:28 +07:00
Visman
ef8e34c861 Pages\Forum: Display feed link only for forums that have posts 2023-11-18 12:28:08 +07:00
Visman
a5c27acb2a Post\Feed: Fix for empty list 2023-11-18 12:00:44 +07:00
Visman
70e196eefd Apply the same type of check for redirect_url 2023-11-17 21:28:10 +07:00
Visman
fe532b6990 Admin\Forums: Add regex for redirect_url 2023-11-17 21:11:29 +07:00
Visman
85e2c3ed29 Skip forums-redirect when selecting unfollow 2023-11-17 18:53:52 +07:00
Visman
3299b641a7 Inline not use max-width 2023-11-17 18:51:37 +07:00
Visman
b7001bc83c link 2023-11-17 18:51:02 +07:00
Visman
baaecfcea7 Delete apc_delete_file() 2023-11-15 20:48:50 +07:00
Visman
164e0f8653 Downgrade CSP status for PM from secury to common 2023-11-15 17:15:31 +07:00
Visman
56160a3a94 Extensions: Add Log->debug 2023-11-15 14:11:09 +07:00
Visman
e98a014f24 Change form validation rules for arrays 2023-11-14 20:51:42 +07:00
Visman
5dc6ecfa23 Core\Validator: Add support for multidimensional rules arrays 2023-11-14 20:21:51 +07:00
Visman
4bd6f93161 Core\Func: Fix FRIENDLY URL for update 2023-11-14 08:46:17 +07:00
Visman
568e119a79 Extensions: fix call to set/remove symlinks 2023-11-13 23:12:35 +07:00
Visman
06486e890b Fix button 2023-11-13 23:11:06 +07:00
Visman
3d6501ac7c Update .dist.htaccess 2023-11-13 20:34:48 +07:00
Visman
b39197b70e Extensions: Add symlinks support
https://github.com/forkbb/forkbb/issues/13
Example: 642109d7c0
2023-11-13 20:13:40 +07:00
Visman
26cd5d3c17 Update .gitignore 2023-11-13 18:03:41 +07:00
Visman
47882fb1d1 Update readme.md 2023-11-12 15:33:26 +07:00
Visman
c2be23603a Pages\Profile\View: Add nofollow for post/topics links 2023-11-11 21:34:33 +07:00
Visman
fadf4098ba Revert "Update Page.php"
This reverts commit b3d238c1cd.
2023-11-11 21:16:45 +07:00
Visman
1ac5847399 Fix slow slow request for feed 2023-11-11 21:09:32 +07:00
Visman
b3d238c1cd Update Page.php 2023-11-11 20:27:37 +07:00
Visman
7ef1e68af7 Add a little rigor to Curl 2023-11-11 19:12:45 +07:00
Visman
d10d8aa2c9 Fix OAuth
fix 9cfd336e7f
2023-11-10 17:24:11 +07:00
Visman
032301df17 Update readme.md 2023-11-10 11:46:58 +07:00
Visman
113df48a3c Up rev 2023-11-10 11:45:54 +07:00
Visman
a0eb7a0e27 Create Sitemap page 2023-11-10 11:44:53 +07:00
Visman
1d57ade40f Pages\Misc: Change sitemap() method 2023-11-10 10:21:25 +07:00
Visman
fabc46f8ee Add rel="nofollow" to topic and forum templates 2023-11-09 22:11:16 +07:00
Visman
2c87b98d24 Pages\Misc: Change sitemap() method 2023-11-09 20:24:07 +07:00
Visman
2feaff7b5c Take into account in the online list the person who accessed the sitemap 2023-11-08 20:29:18 +07:00
Visman
956a2b2d67 Add sitemap.xml for test 2023-11-08 20:05:16 +07:00
Visman
5ebf9eb3f6 Core\Router: Fix for dynamic files in the forum root
Example: /sitemap.xml, /sitemap1.xml, /sitemap542.xml
2023-11-08 19:58:04 +07:00
Visman
c648a52651 Revert "Core\Router: Fix for dynamic files in the forum root"
This reverts commit d4969ae009.
2023-11-08 19:29:14 +07:00
Visman
d4969ae009 Core\Router: Fix for dynamic files in the forum root
Example: /sitemap.xml, /sitemap1.xml, /sitemap542.xml
2023-11-08 16:28:33 +07:00
Visman
40563b4ffc Admin\Maintenance: Resets the contents of the opcode cache after clearing the engine cache
Is it necessary to do this?
2023-11-07 15:58:00 +07:00
Visman
5e1e956de6 Core\Func: Change friendly() method 2023-11-07 15:52:28 +07:00
Visman
7a2efd3bd5 Change default transliteration
With these initial settings, only the character substitution array from translit.default.php will be used for transliteration. The Transliterator class will not be used.
https://forkbb.ru/post/280#p280
2023-11-06 22:22:28 +07:00
Visman
f40602fd82 Up rev 2023-11-04 23:06:39 +07:00
Visman
223efdfb8f Use friendly_name in code 2023-11-04 22:23:12 +07:00
Visman
ced3c7cd15 Add friendly_name field to ::forums table 2023-11-04 21:38:02 +07:00
Visman
4495f64268 Add character substitution file for transliteration 2023-11-04 19:36:25 +07:00
Visman
0e8e5cd87a Fix for #27 2023-11-04 18:25:58 +07:00
Visman
2e64177610 Core\Router: Do less calculations in link() method 2023-11-03 15:44:40 +07:00
Visman
432a441a2c Minor change 2023-11-03 13:32:25 +07:00
Visman
5a3ad9d33e Core\Files: Optimize the filterName() method 2023-11-03 13:32:13 +07:00
Visman
2ddd7796b0 Core\Func: Optimize the friendly() method
Removed the transliterator_transliterate() function since it initializes a new Transliterator every time.
Currently, Transliterator is initialized once with the rules set, and then only the transliterate() method is used.
2023-11-03 12:08:23 +07:00
Visman
9f1d781beb Remove repeated hyphens 2023-11-02 19:38:59 +07:00
Visman
d5eec724d6 Up rev 2023-11-02 19:02:05 +07:00
Visman
acaad2db29 Fix for the previous 2023-11-02 18:56:07 +07:00
Visman
8e3c74367e Add settings for friendly url
https://forkbb.ru/post/280#p280
2023-11-02 18:44:32 +07:00
Visman
e6d66f7e0a Core\Files: Change file name transliteration 2023-11-02 16:58:26 +07:00
Visman
4f2c637134 Admin\Logs: Clear context 2023-11-01 09:40:07 +07:00
Visman
c5f2aa0a97 config\jevix.default: fix rel attribute in a tag
#22
2023-10-31 19:58:34 +07:00
Visman
5ac2b20ff2 Replace base64 images in messages to uploaded files
For users who have permission to upload files.
2023-10-30 20:08:59 +07:00
Visman
414e3d9717 Core\Files: change the verification logic and extend the uploadFromLink() method 2023-10-30 19:05:25 +07:00
Visman
25691fa3af Update form.forkbb.php 2023-10-30 12:58:19 +07:00
Visman
c0bb06dc13 Templates: use php/endphp + if(empty()) to empty() 2023-10-30 12:42:03 +07:00
Visman
4bde2ad136 templates\extensions: fix + change for empty list 2023-10-29 20:31:59 +07:00
Visman
1d917f0151 View\Compiler: add @php and @endphp 2023-10-29 20:31:00 +07:00
Visman
d64b016637 Update readme.md 2023-10-29 19:08:40 +07:00
Visman
1c7d91b643 Update admin.po 2023-10-28 22:03:32 +07:00
Visman
da79516766
Merge pull request #25 from forkbb/Extensions
Extensions
2023-10-28 21:46:07 +07:00
Visman
327c5cfb1a Update .gitignore 2023-10-16 18:10:09 +07:00
Visman
b57a66f0cf Models\Manager: Add access to the repository property 2023-10-16 18:07:40 +07:00
Visman
3891d8fced Minor changes 2023-10-14 22:34:24 +07:00
Visman
0cf80df852 Core\Validator: Fix addRules() method for array 2023-10-14 22:32:59 +07:00
78 changed files with 2183 additions and 329 deletions

View file

@ -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
View file

@ -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

View file

@ -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

View file

@ -46,7 +46,6 @@ class Primary
$confChange = [
'multiple' => [
'CtrlRouting' => \ForkBB\Controllers\Update::class,
'AdminUpdate' => \ForkBB\Models\Pages\Admin\Update::class,
],
];

View file

@ -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'
);
}
// только админ

View file

@ -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);
}
}

View 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;

View file

@ -226,10 +226,12 @@ class DB
case 's':
case 'f':
$value = [1];
break;
default:
$value = [1];
$type = 's';
break;
}

View file

@ -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>";
}

View file

@ -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);

View file

@ -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 : '-';
}
}

View file

@ -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);

View file

@ -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) {

View file

@ -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);
}
}
}

View file

@ -505,4 +505,20 @@ EOD;
{
return "<?php break; ?>";
}
/**
* @php
*/
protected function compilePhp(): string
{
return "<?php";
}
/**
* @endphp
*/
protected function compileEndphp(): string
{
return " ?>";
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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');
}

View file

@ -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,
]
);
}

View file

@ -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);
}
/**

View file

@ -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

View file

@ -44,7 +44,7 @@ class UpdateUsername extends Action
$isMod = true;
$forum->modAdd($user); // переименование модератора
$this->c->forums->update($forum);
$this->manager->update($forum);
}
}

View file

@ -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;
}
}

View file

@ -37,7 +37,7 @@ class Info extends Method
'User',
[
'id' => $id,
'name' => $name,
'name' => $this->c->Func->friendly($name),
]
)
: null,

View file

@ -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'

View file

@ -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([

View file

@ -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,

View file

@ -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],

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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}",
]

View file

@ -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,

View file

@ -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}"] = [

View file

@ -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';
}
/**

View file

@ -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',
]);

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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}"] = [

View file

@ -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}"] = [

View file

@ -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'] = [

View file

@ -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;
}

View 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;
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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),
]
);
}

View file

@ -168,7 +168,7 @@ class User extends DataModel
'User',
[
'id' => $this->id,
'name' => $this->username,
'name' => $this->c->Func->friendly($this->username),
]
);
}

View file

@ -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;

View file

@ -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',

View file

@ -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],
],

View file

@ -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,

File diff suppressed because it is too large Load diff

View file

@ -88,4 +88,4 @@ msgid "Maintenance only"
msgstr "Available only in maintenance mode."
msgid "Extensions"
msgstr "Расширения"
msgstr "Extensions"

View file

@ -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."

View file

@ -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."

View file

@ -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' уже существует."

View file

@ -134,3 +134,9 @@ msgstr "Если ДА, то новые сообщения пользовател
msgid "<span></span>"
msgstr "<span></span>"
msgid "Friendly name label"
msgstr "Имя для URL"
msgid "Friendly name help"
msgstr "Строковый идентификатор на основе которого будет сформирован адрес указывающий на данный раздел. Может состоять из латинских букв, цифр, знаков подчеркивания и дефисов."

View file

@ -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>

View file

@ -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')

View file

@ -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

View file

@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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">

View 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>

View 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>

View file

@ -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

View file

@ -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 -->

View file

@ -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>

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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/
```