Compare commits

...

53 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
56 changed files with 1794 additions and 212 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

@ -1271,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 = [
@ -1291,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

@ -51,11 +51,11 @@ class Func
/**
* Массив подмены символов перед/вместо траслитератор(ом/а)
*/
protected ?array $translArray;
protected ?array $translArray = null;
public function __construct(protected Container $c)
{
$this->fUrl = $this->c->FRIENDLY_URL;
$this->fUrl = $c->isInit('FRIENDLY_URL') ? $c->FRIENDLY_URL : [];
}
/**
@ -325,38 +325,25 @@ class Func
*/
public function friendly(string $str): string
{
if (null === $this->transl) {
$useFile = false;
if (! empty($this->fUrl['translit'])) {
if (! empty($this->fUrl['file'])) {
$this->translArray ??= include "{$this->c->DIR_CONFIG}/{$this->fUrl['file']}";
if (empty($this->fUrl['translit'])) {
$this->transl = false;
} elseif (true === $this->fUrl['translit']) {
$this->transl = false;
$useFile = true;
} else {
$this->transl = Transliterator::create($this->fUrl['translit']) ?? false;
if ($this->transl instanceof Transliterator) {
$useFile = true;
}
$str = \strtr($str, $this->translArray);
}
if (
true === $useFile
&& ! empty($this->fUrl['file'])
\is_string($this->fUrl['translit'])
&& \preg_match('%[\x80-\xFF]%', $str)
) {
$this->translArray = include "{$this->c->DIR_CONFIG}/{$this->fUrl['file']}";
$this->transl ??= Transliterator::create($this->fUrl['translit']) ?? false;
if ($this->transl instanceof Transliterator) {
$str = $this->transl->transliterate($str);
}
}
}
if (! empty($this->translArray)) {
$str = \strtr($str, $this->translArray);
}
if ($this->transl instanceof Transliterator) {
$str = $this->transl->transliterate($str);
}
if (true === $this->fUrl['lowercase']) {
$str = \mb_strtolower($str, 'UTF-8');
}

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

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

@ -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);
}
}
@ -101,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);
}
}
@ -118,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);
}
}
@ -360,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,

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

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

@ -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([
@ -366,7 +367,7 @@ class Forums extends Admin
'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',

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

@ -1048,4 +1048,22 @@ class Update extends Admin
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

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

View file

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

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

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

@ -70,7 +70,7 @@ if (
$c->BASE_URL = \str_replace('https://', 'http://', $c->BASE_URL);
}
$c->FORK_REVISION = 72;
$c->FORK_REVISION = 73;
$c->START = $forkStart;
$c->PUBLIC_URL = $c->BASE_URL . $forkPublicPrefix;

View file

@ -62,7 +62,7 @@ return [
'TIME_FORMATS' => ['H:i:s', 'H:i', 'H:i:s', 'H:i', 'g:i:s a', 'g:i a'],
'FRIENDLY_URL' => [
'lowercase' => true,
'translit' => 'Russian-Latin/BGN;Any-Latin;Latin-ASCII;',
'translit' => true, // 'Any-Latin;Latin-ASCII;',
'WtoHyphen' => true,
'file' => 'translit.default.php',
],

View file

@ -76,7 +76,7 @@ return [
'TIME_FORMATS' => ['H:i:s', 'H:i', 'H:i:s', 'H:i', 'g:i:s a', 'g:i a'],
'FRIENDLY_URL' => [
'lowercase' => true,
'translit' => 'Russian-Latin/BGN;Any-Latin;Latin-ASCII;',
'translit' => true, // 'Any-Latin;Latin-ASCII;',
'WtoHyphen' => true,
'file' => 'translit.default.php',
],
@ -372,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

@ -119,3 +119,15 @@ msgstr "An error occurred in updateCommon."
msgid "Empty"
msgstr "Empty"
msgid "Invalid symlink type"
msgstr "Invalid symlink type."
msgid "Bad symlink"
msgstr "Bad symlink."
msgid "Target '%s' not found"
msgstr "Target '%s' not found."
msgid "Link '%s' already exists"
msgstr "Link '%s' already exists."

View file

@ -119,3 +119,15 @@ msgstr "Возникла ошибка в updateCommon."
msgid "Empty"
msgstr "Пусто"
msgid "Invalid symlink type"
msgstr "Неверный тип символической ссылки."
msgid "Bad symlink"
msgstr "Плохая символическая ссылка."
msgid "Target '%s' not found"
msgstr "Target '%s' отсутствует."
msgid "Link '%s' already exists"
msgstr "Link '%s' уже существует."

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>
@ -205,7 +205,7 @@
@endif
@if ($p->model->canCreateTopic)
<small>|</small>
<span class="f-act-span"><a class="f-btn f-btn-create-topic" title="{{ __('Post topic') }}" href="{{ $p->model->linkCreateTopic }}"><span>{!! __('Post topic') !!}</span></a></span>
<span class="f-act-span"><a class="f-btn f-btn-create-topic" title="{{ __('Post topic') }}" href="{{ $p->model->linkCreateTopic }}" rel="nofollow"><span>{!! __('Post topic') !!}</span></a></span>
@endif
</div>
@endif

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

@ -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
@ -164,7 +164,7 @@
@endif
@if ($post->canQuote)
<small>-</small>
<a class="f-btn f-postquote" title="{{ __('Quote') }}" href="{{ $post->linkQuote }}"><span>{!! __('Quote') !!}</span></a>
<a class="f-btn f-postquote" title="{{ __('Quote') }}" href="{{ $post->linkQuote }}" rel="nofollow"><span>{!! __('Quote') !!}</span></a>
@endif
</aside>
@endif
@ -191,7 +191,7 @@
@endif
@if ($p->model->canReply)
<small>|</small>
<span class="f-act-span"><a class="f-btn f-btn-post-reply" title="{{ __('Post reply') }}" href="{{ $p->model->linkReply }}"><span>{!! __('Post reply') !!}</span></a></span>
<span class="f-act-span"><a class="f-btn f-btn-post-reply" title="{{ __('Post reply') }}" href="{{ $p->model->linkReply }}" rel="nofollow"><span>{!! __('Post reply') !!}</span></a></span>
@endif
</div>
@endif

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;
@ -1338,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.72 Alpha Readme
# ForkBB rev.73 Alpha Readme
## About
@ -13,7 +13,7 @@ ForkBB is a free and open source forum software. The project is based on [FluxBB
## Install
Topic [Установка (Installation)](https://forkbb.ru/topic/28/%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0%20%28Installation%29)
Topic [Установка (Installation)](https://forkbb.ru/topic/28/ustanovka-installation)
### For Apache:
@ -43,6 +43,8 @@ Disallow: /reg
Disallow: /search
Disallow: /post
Disallow: /forum/scroll
Disallow: /forum/*/new/topic
Disallow: /topic/*/new/reply
Disallow: /userlist/*/DESC/
Disallow: /userlist/*/ASC/
```