Compare commits

..

No commits in common. "master" and "Extensions" have entirely different histories.

78 changed files with 323 additions and 2177 deletions

View file

@ -4,9 +4,6 @@
#
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,32 +1,19 @@
/_*
/.htaccess
/index.php
/app/config/main.php
/app/config/_*
/app/config/db/*
/app/config/ext/*
/app/cache/*
/app/cache/**/*.php
/app/cache/**/*.lock
/app/cache/**/*.tmp
/app/log/*
/ext/*
/public/img/avatars/*
/public/img/og/*
/public/.htaccess
/public/index.php
/public/img/*
/public/style/*
/public/upload/**/*
!/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/style/ForkBB_old/*
!/public/upload/index.html
!.gitkeep

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2023 Visman (mio.visman@yandex.ru, https://github.com/MioVisman)
Copyright (c) 2017-2023 Visman (mio.visman@yandex.ru)
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,6 +46,7 @@ class Primary
$confChange = [
'multiple' => [
'CtrlRouting' => \ForkBB\Controllers\Update::class,
'AdminUpdate' => \ForkBB\Models\Pages\Admin\Update::class,
],
];

View file

@ -29,13 +29,6 @@ class Routing
$config = $this->c->config;
$r = $this->c->Router;
$r->add(
$r::GET,
'/sitemap{id:\d*}.xml',
'Sitemap:view',
'Sitemap'
);
// регистрация/вход/выход
if ($user->isGuest) {
// вход
@ -104,18 +97,6 @@ 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,
@ -123,6 +104,15 @@ class Routing
'RegLog:redirect',
'RegLogRedirect'
);
if ($user->isAdmin) {
$r->add(
$r::GET,
'/reglog/callback/{name}',
'RegLog:callback',
'RegLogCallback'
);
}
}
// просмотр разрешен
@ -639,6 +629,7 @@ class Routing
'Moderate:action',
'Moderate'
);
}
// только админ

View file

@ -226,6 +226,8 @@ 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,7 +172,6 @@ class Config
switch ($type) {
case 'ZERO':
$type = 'NEW';
break;
case 'NEW':
case '=>':
@ -181,7 +180,6 @@ class Config
$value_before = $other;
$other = '';
$type = 'VALUE';
break;
default:
throw new ForkException('Config array cannot be parsed (3)');
@ -221,7 +219,6 @@ class Config
case 'VALUE':
case 'VALUE_OR_KEY':
$type = 'NEW';
break;
default:
throw new ForkException('Config array cannot be parsed (6)');
@ -237,7 +234,6 @@ class Config
$value = null;
$value_before = '';
$type = '=>';
break;
default:
throw new ForkException('Config array cannot be parsed (7)');
@ -255,8 +251,7 @@ class Config
case 'VALUE_OR_KEY':
case 'VALUE':
case '=>':
$other .= $token;
$other .= $token;
break;
default:
throw new ForkException('Config array cannot be parsed (8)');
@ -296,11 +291,9 @@ class Config
}
$type = 'VALUE_OR_KEY';
break;
case '=>':
$type = 'VALUE';
break;
default:
throw new ForkException('Config array cannot be parsed (10)');
@ -318,11 +311,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);
}
/**
@ -436,7 +429,6 @@ class Config
return false;
} else {
$result = $config[$key];
unset($config[$key]);
return $result;

View file

@ -226,12 +226,10 @@ 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();
\ob_start();
$this->obLevel = \ob_get_level();
}
@ -198,7 +198,6 @@ class ErrorHandler
if (isset($error['exception'])) {
$context['exception'] = $error['exception'];
}
$context['headers'] = false;
$this->c->Log->{$method}($this->message($error), $context);
@ -280,19 +279,15 @@ 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;
}
@ -301,8 +296,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,7 +15,6 @@ use ForkBB\Core\File;
use ForkBB\Core\Image;
use ForkBB\Core\Image\DefaultDriver;
use ForkBB\Core\Exceptions\FileException;
use Transliterator;
use InvalidArgumentException;
use RuntimeException;
@ -56,11 +55,6 @@ class Files
*/
protected array $tmpFiles = [];
/**
* Для кэширования транслитератора
*/
protected Transliterator|false|null $transl = null;
/**
* Список mime типов считающихся картинками
*/
@ -977,17 +971,18 @@ class Files
*/
public function filterName(string $name): string
{
if (null === $this->transl) {
$this->transl = Transliterator::create('Any-Latin;Latin-ASCII;Lower();') ?? false;
$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 ($this->transl instanceof Transliterator) {
$name = $this->transl->transliterate($name);
}
$name = \trim(\preg_replace(['%[^\w]+%', '%_+%'], ['-', '_'], $name), '-_');
return isset($name[0]) ? $name : (string) \time();
return $name;
}
/**
@ -1100,16 +1095,32 @@ class Files
protected function uploadFile(array $file, bool $isUploaded = true): ?File
{
if (\UPLOAD_ERR_OK !== $file['error']) {
$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',
};
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;
}
return null;
}
@ -1137,22 +1148,16 @@ class Files
$ext = \mb_strtolower(\substr($file['name'], $pos + 1), 'UTF-8');
}
$mimeType = $this->mimeType($file['tmp_name']);
$imageExt = $this->imageExt($file['tmp_name']);
if (! isset($this->mimeToExt[$mimeType])) {
$this->error = "Unknown mime type of the file: {$mimeType}";
return null;
}
if (isset($this->imageType[$mimeType])) {
if (\is_string($imageExt)) {
if ($file['size'] > $this->maxImgSize) {
$this->error = 'The image too large';
return null;
}
$ext = $this->imageType[$mimeType];
$ext = $imageExt;
$className = Image::class;
} else {
if ($file['size'] > $this->maxFileSize) {
@ -1164,6 +1169,14 @@ 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}/")) {
@ -1188,32 +1201,21 @@ class Files
}
/**
* Получает файл по внешней ссылке или из строки data:...;base64,...
* Получает файл по внешней ссылке
*/
public function uploadFromLink(string $url): ?File
{
if (\preg_match('%^data:(.*?);base64,%', $url, $matches)) {
$name = '';
$type = $matches[1];
$offset = \strlen($matches[0]);
} else {
$cmpn = \parse_url($url);
$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;
}
$name = \basename($cmpn['path']) ?: '';
$type = '';
$offset = 0;
return null;
}
$tmpName = $this->c->DIR_CACHE . '/' . $this->c->Secury->randomPass(32) . '.tmp';
$this->addTmpFile($tmpName);
@ -1228,21 +1230,7 @@ class Files
$result = null;
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')) {
if (\extension_loaded('curl')) {
$result = $this->curlAction($url, $tmpFile);
} elseif (\filter_var(\ini_get('allow_url_fopen'), \FILTER_VALIDATE_BOOL)) {
$result = $this->streamAction($url, $tmpFile);
@ -1254,8 +1242,8 @@ class Files
return $this->uploadFile(
[
'tmp_name' => $tmpName,
'name' => $name,
'type' => $type,
'name' => \basename($cmpn['path']) ?: '',
'type' => '',
'error' => \UPLOAD_ERR_OK,
'size' => \filesize($tmpName),
],
@ -1271,7 +1259,7 @@ class Files
/**
* Переменные конфига подключения
*/
protected int $actMaxRedir = 5;
protected int $actMaxRedir = 10;
protected float $actTimeout = 15.0;
protected string $actUAgent = 'ForkBB downloader (%s)';
protected array $actHeader = [
@ -1291,8 +1279,6 @@ 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,7 +13,6 @@ namespace ForkBB\Core;
use ForkBB\Core\Container;
use DateTime;
use DateTimeZone;
use Transliterator;
use function \ForkBB\__;
class Func
@ -38,24 +37,8 @@ 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 : [];
}
/**
@ -319,39 +302,4 @@ 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,10 +103,11 @@ class Mail
return false;
}
if (true === $strict) {
if ($strict) {
$level = $this->c->ErrorHandler->logOnly(\E_WARNING);
if (\is_string($ip)) {
if ($ip) {
$mx = \checkdnsrr($ip, 'MX'); // ipv6 в пролёте :(
} else {
$mx = \dns_get_record($domainASCII, \DNS_MX);

View file

@ -149,9 +149,7 @@ class Router
'page' !== $name
|| 1 !== $args[$name]
) {
$data['{' . $name . '}'] = \is_integer($args[$name])
? (string) $args[$name]
: \rawurlencode(\str_replace($this->subSearch, $this->subRepl, (string) $args[$name]));
$data['{' . $name . '}'] = \rawurlencode(\str_replace($this->subSearch, $this->subRepl, (string) $args[$name]));
continue;
}
@ -234,12 +232,7 @@ class Router
}
$pos = \strpos($uri, '/', 1);
if (false === $pos) {
$base = isset($this->dynamic[$uri]) ? $uri : '/';
} else {
$base = \substr($uri, 0, $pos);
}
$base = false === $pos ? $uri : \substr($uri, 0, $pos);
if (isset($this->dynamic[$base])) {
foreach ($this->dynamic[$base] as $pattern => $data) {

View file

@ -140,7 +140,13 @@ class Validator
public function addRules(array $list): Validator
{
foreach ($list as $field => $raw) {
$rules = [];
$rules = [];
$suffix = null;
// правило для элементов массива
if (\strpos($field, '.') > 0) {
list($field, $suffix) = \explode('.', $field, 2);
}
if (! \is_array($raw)) {
$raw = \explode('|', $raw);
@ -176,34 +182,13 @@ class Validator
$rules[$name] = $rule ?? '';
}
if (\strpos($field, '.') > 0) {
$fields = \explode('.', $field);
$n = \count($fields);
$start = true;
$r = &$this->rules;
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);
if (isset($suffix)) {
$this->rules[$field]['array'][$suffix] = $rules;
} else {
$this->rules[$field] = $rules;
$this->fields[$field] = $field;
$this->rules[$field] = $rules;
}
$this->fields[$field] = $field;
}
return $this;
@ -654,32 +639,35 @@ class Validator
if ('' === $name) {
$result = $this->checkValue($value, $rules, $field);
} else {
if (false !== \strpos($name, '.')) {
if (! \preg_match('%^([^.]+)(?:\.(.+))?$%', $name, $matches)) {
throw new RuntimeException("Bad path '{$name}'");
}
$key = $matches[1];
$name = $matches[2] ?? '';
if (
'*' === $name
'*' === $key
&& \is_array($value)
) {
foreach ($value as $i => $cur) {
$this->recArray($value[$i], $result[$i], '', $rules, $field);
$this->recArray($value[$i], $result[$i], $name, $rules, $field);
}
} elseif (
'*' !== $name
'*' !== $key
&& \is_array($value)
&& \array_key_exists($name, $value)
&& \array_key_exists($key, $value)
) {
$this->recArray($value[$name], $result[$name], '', $rules, $field);
$this->recArray($value[$key], $result[$key], $name, $rules, $field);
} elseif (isset($rules['required'])) {
$tmp1 = null;
$tmp2 = null;
$this->recArray($tmp1, $tmp2, '', $rules, $field);
} elseif ('*' === $name) {
$this->recArray($tmp1, $tmp2, $name, $rules, $field);
} elseif ('*' === $key) {
$result = []; // ???? а может там не отсутствие элемента, а не array?
} else {
$value[$name] = null;
$this->recArray($value[$name], $result[$name], '', $rules, $field);
$value[$key] = null;
$this->recArray($value[$key], $result[$key], $name, $rules, $field);
}
}
}

View file

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

View file

@ -66,6 +66,8 @@ 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,10 +161,7 @@ class Extension extends Model
$path = $this->fileData['path'] . '/' . \ltrim($cur['file'], '\\/');
if (
$this->c->Files->isBadPath($path)
|| ! \is_file($path)
) {
if (! \is_file($path)) {
return ['Template file \'%s\' not found', $cur['file']];
}
@ -184,49 +181,6 @@ 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,10 +150,6 @@ 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',
@ -168,28 +164,15 @@ class Extensions extends Manager
$result = [];
foreach ($files as $path => $file) {
$context = null;
if (! \is_array($file)) {
$context = [
'errors' => ['Bad json'],
];
continue;
} elseif (! $v->validation($file)) {
$context = [
'errors' => \array_map('\\ForkBB\__', $v->getErrorsWithoutType()),
];
continue;
}
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);
}
$data = $v->getData(true);
$data['path'] = $path;
$result[$v->name] = $data;
}
return $result;
@ -255,7 +238,6 @@ class Extensions extends Manager
return false;
}
$this->setSymlinks($ext);
$this->updateIndividual();
$this->c->DB->exec($query, $vars);
@ -290,8 +272,6 @@ class Extensions extends Manager
'fileData' => $ext->fileData,
]);
$this->removeSymlinks($ext);
if (true !== $this->updateCommon($ext)) {
$this->error = 'An error occurred in updateCommon';
@ -360,10 +340,6 @@ class Extensions extends Manager
'fileData' => $ext->fileData,
]);
if ($oldStatus) {
$this->removeSymlinks($ext);
}
if (true !== $this->updateCommon($ext)) {
$this->error = 'An error occurred in updateCommon';
@ -371,7 +347,6 @@ class Extensions extends Manager
}
if ($oldStatus) {
$this->setSymlinks($ext);
$this->updateIndividual();
}
@ -404,7 +379,6 @@ class Extensions extends Manager
'fileData' => $ext->fileData,
]);
$this->setSymlinks($ext);
$this->updateIndividual();
$this->c->DB->exec($query, $vars);
@ -436,7 +410,6 @@ class Extensions extends Manager
'fileData' => $ext->fileData,
]);
$this->removeSymlinks($ext);
$this->updateIndividual();
$this->c->DB->exec($query, $vars);
@ -484,6 +457,8 @@ 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;
@ -569,37 +544,4 @@ 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->manager->get($arg->id) instanceof Forum) {
if (! $this->c->forums->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->manager->get($this->parent_forum_id);
return $this->c->forums->get($this->parent_forum_id);
}
}
@ -47,14 +47,6 @@ 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;
}
/**
* Статус возможности создания новой темы
*/
@ -101,7 +93,7 @@ class Forum extends DataModel
if (\is_array($attr)) {
foreach ($attr as $id) {
$sub[$id] = $this->manager->get($id);
$sub[$id] = $this->c->forums->get($id);
}
}
@ -118,7 +110,7 @@ class Forum extends DataModel
if (\is_array($attr)) {
foreach ($attr as $id) {
$all[$id] = $this->manager->get($id);
$all[$id] = $this->c->forums->get($id);
}
}
@ -137,7 +129,7 @@ class Forum extends DataModel
'Forum',
[
'id' => $this->id,
'name' => $this->friendly,
'name' => $this->forum_name,
]
);
}
@ -269,7 +261,7 @@ class Forum extends DataModel
'User',
[
'id' => $id,
'name' => $this->c->Func->friendly($cur),
'name' => $cur,
]
)
: null,
@ -360,7 +352,7 @@ class Forum extends DataModel
}
}
$attr = $this->manager->create([
$attr = $this->c->forums->create([
'num_topics' => $numT,
'num_posts' => $numP,
'last_post' => $time,
@ -399,7 +391,7 @@ class Forum extends DataModel
'Forum',
[
'id' => $this->id,
'name' => $this->friendly,
'name' => $this->forum_name,
]
);
}

View file

@ -34,7 +34,7 @@ class Forums extends Manager
*/
public function create(array $attrs = []): Forum
{
return $this->c->ForumModel->setManager($this)->setModelAttrs($attrs);
return $this->c->ForumModel->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.friendly_name, f.redirect_url, f.parent_forum_id,
$query = 'SELECT f.cat_id, c.cat_name, f.id, f.forum_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->manager->update($forum);
$this->c->forums->update($forum);
}
}

View file

@ -11,7 +11,6 @@ declare(strict_types=1);
namespace ForkBB\Models;
use ForkBB\Core\Container;
use ForkBB\Models\Manager;
class Model
{
@ -35,11 +34,6 @@ class Model
*/
protected array $zDepend = [];
/**
* Текущий Manager для модели
*/
protected Manager $manager;
public function __construct(protected Container $c)
{
}
@ -171,14 +165,4 @@ 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' => $this->c->Func->friendly($name),
'name' => $name,
]
)
: null,

View file

@ -30,7 +30,6 @@ 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,9 +29,8 @@ class Censoring extends Admin
->addRules([
'token' => 'token:AdminCensoring',
'b_censoring' => 'required|integer|in:0,1',
'form' => 'required|array',
'form.*.search_for' => 'exist|string:trim|max:60',
'form.*.replace_with' => 'exist|string:trim|max:60',
'form.*.search_for' => 'string:trim|max:60',
'form.*.replace_with' => 'string:trim|max:60',
])->addAliases([
])->addArguments([
])->addMessages([

View file

@ -102,7 +102,6 @@ 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([
@ -363,11 +362,10 @@ 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|regex:%^(?:https?://.+)?$%', //???? это поле может быть отключено в форме
'redirect_url' => 'string:trim|max:255', //???? это поле может быть отключено в форме
'no_sum_mess' => 'required|integer|in:0,1',
'perms.*.read_forum' => 'checkbox',
'perms.*.post_replies' => 'checkbox',
@ -381,12 +379,11 @@ class Forums extends Admin
$valid = $v->validation($_POST);
$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;
$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;
if ($v->parent > 0) {
$forum->parent_forum_id = $v->parent;
@ -465,13 +462,6 @@ 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,7 +839,6 @@ 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'] = \preg_replace('%^\s*Array\s*\(\n(.+)\n\)\s*$%s', '$1', \print_r($cur['context'], true));
$cur['context'] = \print_r($cur['context'], true);
}
unset($cur);

View file

@ -331,10 +331,6 @@ 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 = 72;
const LATEST_REV_WITH_DB_CHANGES = 70;
const LOCK_NAME = 'lock_update';
const LOCK_TTL = 1800;
const CONFIG_FILE = 'main.php';
@ -979,91 +979,4 @@ 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->friendly,
'name' => $forum->forum_name,
'page' => $forum->page,
]
);
@ -68,10 +68,7 @@ class Forum extends Page
$this->formMod = $this->formMod($forum);
}
if (
$this->c->config->i_feed_type > 0
&& $forum->num_posts > 0
) {
if ($this->c->config->i_feed_type > 0) {
$feedType = 2 === $this->c->config->i_feed_type ? 'atom' : 'rss';
$this->pageHeader('feed', 'link', 0, [
@ -159,7 +156,7 @@ class Forum extends Page
'Forum',
[
'id' => $forum->id,
'name' => $forum->friendly,
'name' => $forum->forum_name,
'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->Func->friendly($this->c->stats->userLast['username']),
'name' => $this->c->stats->userLast['username'],
]
)
: null,

View file

@ -284,7 +284,7 @@ class Moderate extends Page
'Forum',
[
'id' => $this->curForum->id,
'name' => $this->curForum->friendly,
'name' => $this->curForum->forum_name,
'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' => ! empty($forum->redirect_url),
'disabled' => '' != $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,14 +38,12 @@ 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,7 +85,12 @@ class Post extends Page
}
$this->nameTpl = 'post';
$this->canonical = $forum->linkCreateTopic;
$this->canonical = $this->c->Router->link(
'NewTopic',
[
'id' => $forum->id,
]
);
$this->robots = 'noindex';
$this->formTitle = 'Post new topic';
$this->crumbs = $this->crumbs($this->formTitle, $forum);
@ -152,7 +157,12 @@ class Post extends Page
}
$this->nameTpl = 'post';
$this->canonical = $topic->linkReply;
$this->canonical = $this->c->Router->link(
'NewReply',
[
'id' => $topic->id,
]
);
$this->robots = 'noindex';
$this->formTitle = 'Post a reply';
$this->crumbs = $this->crumbs($this->formTitle, $topic);

View file

@ -10,7 +10,6 @@ declare(strict_types=1);
namespace ForkBB\Models\Pages;
use ForkBB\Core\Image;
use ForkBB\Core\Validator;
use ForkBB\Models\Model;
use function \ForkBB\__;
@ -104,7 +103,10 @@ trait PostValidatorTrait
{
$this->c->Lang->load('validator');
$this->attachmentsProc($marker, $args);
// обработка вложений + хак с добавление вложений в сообщение на лету
if (\is_string($attMessage = $this->attachmentsProc($marker, $args))) {
$_POST['message'] .= $attMessage;
}
$notPM = $this->fIndex !== self::FI_PM;
@ -295,83 +297,55 @@ trait PostValidatorTrait
/**
* Обрабатывает загруженные файлы
*/
protected function attachmentsProc(string $marker, array $args): void
protected function attachmentsProc(string $marker, array $args): ?string
{
if (! $this->userRules->useUpload) {
return;
return null;
}
$v = $this->c->Validator->reset()
->addValidators([
'check_attach' => [$this, 'vCheckAttach'],
'check_attach' => [$this, 'vCheckAttach'],
])->addRules([
'token' => 'token:' . $marker,
'message' => 'string:trim',
'attachments' => "file:multiple|max:{$this->user->g_up_size_kb}|check_attach",
'token' => 'token:' . $marker,
'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;
return null;
} elseif (! \is_array($v->attachments)) {
return null;
}
$calc = false;
$result = "\n";
$calc = false;
// костыль с конвертацией картинок из 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]);
foreach ($v->attachments as $file) {
$data = $this->c->attachments->addFile($file);
if (! $file instanceof Image) {
return $this->c->Files->error() ?? 'Bad image';
}
if (\is_array($data)) {
$name = $file->name();
$calc = true;
$data = $this->c->attachments->addFile($file);
if (\is_array($data)) {
$calc = true;
return "[img]{$data['url']}[/img]";
if ($data['image']) {
$result .= "[img]{$data['url']}[/img]\n"; // ={$name}
} else {
return 'Bad file';
$result .= "[url={$data['url']}]{$name}[/url]\n";
}
},
(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]) || ! empty($this->curForums[$forum->id]->redirect_url),
'disabled' => ! isset($this->curForums[$forum->id]) || '' != $this->curForums[$forum->id]->redirect_url,
'caption' => 'Moderator label',
];
$form['sets']["forum{$forum->id}"] = [

View file

@ -52,15 +52,7 @@ class Search extends Profile
if ($v->validation($_POST)) {
if (! empty($v->follow)) {
$unfollow = [];
foreach ($this->curForums as $id => $forum) {
if (empty($forum->redirect_url)) {
$unfollow[$id] = $id;
}
}
$unfollow = \array_diff($unfollow, $v->follow);
$unfollow = \array_diff(\array_keys($this->curForums), $v->follow);
\sort($unfollow, \SORT_NUMERIC);
@ -194,7 +186,7 @@ class Search extends Profile
'type' => 'checkbox',
'value' => $forum->id,
'checked' => ! isset($this->curUnfollowed[$forum->id]),
'disabled' => ! empty($this->curForums[$forum->id]->redirect_url),
'disabled' => '' != $this->curForums[$forum->id]->redirect_url,
'caption' => 'Follow label',
];
$form['sets']["forum{$forum->id}"] = [

View file

@ -257,10 +257,7 @@ class View extends Profile
];
if ($this->curUser->last_post > 0) {
if (
1 === $this->user->g_search
&& ! $this->user->isBot
) {
if (1 === $this->user->g_search) {
$fields['posts'] = [
'class' => ['pline'],
'type' => 'link',
@ -274,7 +271,6 @@ class View extends Profile
]
),
'title' => __('Show posts'),
'rel' => 'nofollow',
];
$fields['topics'] = [
'class' => ['pline'],
@ -289,7 +285,6 @@ class View extends Profile
]
),
'title' => __('Show topics'),
'rel' => 'nofollow',
];
} elseif ($this->userRules->showPostCount) {
$fields['posts'] = [

View file

@ -501,10 +501,7 @@ class Search extends Page
case 'topics':
case 'topics_subscriptions':
case 'forums_subscriptions':
if (
! isset($uid)
|| $this->user->isBot
) {
if (! isset($uid)) {
break;
}

View file

@ -1,196 +0,0 @@
<?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' => $this->c->Func->friendly($topic->name),
'page' => $topic->page,
'name' => $topic->name,
'page' => $topic->page
]
);
$this->model = $topic;

View file

@ -13,7 +13,6 @@ namespace ForkBB\Models\Post;
use ForkBB\Models\Action;
use ForkBB\Models\Topic\Topic;
use ForkBB\Models\Forum\Forum;
use PDO;
class Feed extends Action
{
@ -51,28 +50,13 @@ class Feed extends Action
$vars = [
':forums' => $ids,
];
$query = 'SELECT p.id
$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 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,9 +267,7 @@ abstract class Driver extends Model
break;
}
\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_MAXREDIRS, 10);
\curl_setopt($ch, \CURLOPT_TIMEOUT, 10);
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, \CURLOPT_HEADER, false);

View file

@ -31,66 +31,49 @@ 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':
$vars = [
':forums' => $forums,
':uid' => $uid,
];
$query = 'SELECT COUNT(p.id)
$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)';
$count = (int) $this->c->DB->query($query, $vars)->fetchColumn();
$this->model->numPages = (int) \ceil(($count ?: 1) / $this->c->user->disp_posts);
WHERE p.poster_id=?i:uid AND t.forum_id IN (?ai:forums)
ORDER BY p.posted DESC';
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 [];
}
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;
}
$this->model->idsList = $this->model->slice(
$list,
($this->model->page - 1) * $this->c->user->disp_posts,
(int) $this->c->user->disp_posts
);
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 ::topics AS t
INNER JOIN ::posts AS p ON t.id=p.topic_id
FROM forum_topics AS t
INNER JOIN forum_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->c->Func->friendly($this->name),
'name' => $this->name,
]
);
}
@ -329,7 +329,7 @@ class Topic extends DataModel
'Topic',
[
'id' => $this->id,
'name' => $this->c->Func->friendly($this->name),
'name' => $this->name,
]
);
}

View file

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

View file

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

View file

@ -60,12 +60,6 @@ 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', 'rel']],
['a', ['class', 'title', 'href']],
['abbr', ['class']],
['address', ['class']],
['article', ['class']],
@ -110,7 +110,7 @@ return [
// [тег, атрибут, значение, перезапись существующего атрибута]
// [tag, attribute, value, overwrite existing attribute]
'cfgSetTagParamDefault' => [
// ['a', 'rel', 'ugc', false],
['a', 'rel', 'ugc', true],
['img', 'alt', 'image', false],
['img', 'loading', 'lazy', true],
],

View file

@ -74,12 +74,6 @@ 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__ . '/../..'),
@ -372,7 +366,6 @@ 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 "Extensions"
msgstr "Расширения"

View file

@ -116,18 +116,3 @@ 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,9 +134,3 @@ 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,18 +116,3 @@ 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,9 +134,3 @@ 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>
@forelse ($p->extensions as $ext)
@foreach ($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,7 +85,6 @@
<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']))
@ -108,10 +107,9 @@
</dl>
@endforeach
</fieldset>
<fieldset class="f-extfs-confirm">
<fieldset calss="f-extfs-confirm">
<dl>
<dt class="f-extdt-empty"></dt>
<dd class="f-extdd-confirm">
<dd>
<label class="f-flblch"><input name="confirm" class="f-ychk" type="checkbox" value="1">{!! __('Confirm action') !!}</label>
</dd>
</dl>
@ -142,12 +140,7 @@
</div>
</details>
</li>
@empty
<li>
@php $iswev = [FORK_MESS_INFO => ['Empty']]; @endphp
@include ('layouts/iswev')
</li>
@endforelse
@endforeach
</ol>
</fieldset>
</div>

View file

@ -38,8 +38,7 @@
<section id="fork-uploads-files" class="f-admin">
<h2>{!! __('File list head') !!}</h2>
<div class="f-fdiv">
@if (null !== $p->badPage)
@php $iswev = [FORK_MESS_ERR => [['Page %s missing', $p->badPage]]]; @endphp
@if (null !== $p->badPage && $iswev = [FORK_MESS_ERR => [['Page %s missing', $p->badPage]]])
@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 }}" rel="nofollow"><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 }}"><span>{!! __('Post topic') !!}</span></a></span>
</div>
@endif
</div>
@ -87,8 +87,7 @@
<div class="f-hcell f-clast">{!! __('Last post') !!}</div>
</li>
@foreach ($p->topics as $id => $topic)
@if (empty($topic->id))
@php $iswev = [FORK_MESS_ERR => [['Topic %s was not found in the database', $id]]]; @endphp
@if (empty($topic->id) && $iswev = [FORK_MESS_ERR => [['Topic %s was not found in the database', $id]]])
<li id="topic-{{ $id }}" class="f-row">
@include ('layouts/iswev')
</li>
@ -205,7 +204,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 }}" rel="nofollow"><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 }}"><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">t = {{ num(\microtime(true) - $p->start, 3) }} : q = {{ $p->numQueries}} : m = {{ size(\memory_get_usage()) }} / {{ size(\memory_get_peak_usage()) }}</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>
@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.include.', ".{$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.', ".{$cur['type']}.")) for="id-{{ $key }}" @endif>{!! __($cur['caption']) !!}</label>
@endif
</dt>
<dd>

View file

@ -1,21 +1,20 @@
@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>
@empty ($p->blockList)
@php $iswev = [FORK_MESS_INFO => ['No blocked users']]; @endphp
@include ('layouts/iswev')
@if (empty($p->blockList) && $iswev = [FORK_MESS_INFO => ['No blocked users']])
@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>
@endempty
@endif
</section>

View file

@ -41,8 +41,7 @@
<section id="fork-topic" class="f-pm f-pmtopic">
<h2>{{ $p->model->name }}</h2>
@foreach ($p->posts as $id => $post)
@if (empty($post->id))
@php $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]]; @endphp
@if (empty($post->id) && $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]])
@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,9 +29,8 @@
@endif
<section id="fork-forum" class="f-pm f-pmview @empty ($p->pmList) f-pm-empty @endempty">
<h2>{!! __($p->title) !!}</h2>
@empty ($p->pmList)
@php $iswev = [FORK_MESS_INFO => ['Info zero']]; @endphp
@include ('layouts/iswev')
@if (empty($p->pmList) && $iswev = [FORK_MESS_INFO => ['Info zero']])
@include ('layouts/iswev')
@else
<div class="f-ftlist">
<ol class="f-table">
@ -41,10 +40,9 @@
<div class="f-hcell f-clast">{!! __('Last post') !!}</div>
</li>
@foreach ($p->pmList as $id => $topic)
@empty ($topic->id)
@php $iswev = [FORK_MESS_ERR => [['Dialogue %s was not found in the database', $id]]]; @endphp
@if (empty($topic->id) && $iswev = [FORK_MESS_ERR => [['Dialogue %s was not found in the database', $id]]])
<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">
@ -87,11 +85,11 @@
<span class="f-clposter">{!! __(['by %s', $topic->last_poster]) !!}</span>
</div>
</li>
@endempty
@endif
@endforeach
</ol>
</div>
@endempty
@endif
</section>
@if ($p->pagination || $p->form)
<div class="f-pm f-nav-links">

View file

@ -1,11 +0,0 @@
<?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

@ -1,11 +0,0 @@
<?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 }}" rel="nofollow"><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 }}"><span>{!! __('Post reply') !!}</span></a></span>
@endif
</div>
@endif
@ -54,8 +54,7 @@
<section id="fork-topic" class="f-main">
<h2>{!! __('Post list') !!}</h2>
@foreach ($p->posts as $id => $post)
@empty ($post->id))
@php $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]]; @endphp
@if (empty($post->id) && $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]])
@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">
@ -164,13 +163,13 @@
@endif
@if ($post->canQuote)
<small>-</small>
<a class="f-btn f-postquote" title="{{ __('Quote') }}" href="{{ $post->linkQuote }}" rel="nofollow"><span>{!! __('Quote') !!}</span></a>
<a class="f-btn f-postquote" title="{{ __('Quote') }}" href="{{ $post->linkQuote }}"><span>{!! __('Quote') !!}</span></a>
@endif
</aside>
@endif
</div>
</article>
@endempty
@endif
@endforeach
</section>
<!-- PRE mainAfter -->
@ -191,7 +190,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 }}" rel="nofollow"><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 }}"><span>{!! __('Post reply') !!}</span></a></span>
@endif
</div>
@endif

View file

@ -41,8 +41,7 @@
<section id="fork-topic-ins" class="f-main">
<h2>{!! __('Post list') !!}</h2>
@foreach ($p->posts as $id => $post)
@empty ($post->id))
@php $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]]; @endphp
@if (empty($post->id) && $iswev = [FORK_MESS_ERR => [['Message %s was not found in the database', $id]]])
@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">
@ -99,7 +98,7 @@
</aside>
</div>
</article>
@endempty
@endif
@endforeach
</section>
<!-- PRE mainAfter -->

View file

@ -4,9 +4,6 @@
#
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,6 +10,17 @@
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;
@ -1286,12 +1297,7 @@
padding: 0.3125rem;
}
#fork-extsinfo .f-extdt-empty {
display: none;
}
#fork-extsinfo .f-extdd-author,
#fork-extsinfo .f-extdd-confirm {
#fork-extsinfo .f-extdd-author {
width: 100%;
}
@ -1327,6 +1333,6 @@
width: auto;
}
#forka .f-fbtn[name="uninstall"]:not(.origin) {
#forka .f-fbtn[data-name="uninstall"]:not(.origin) {
color: red;
}

View file

@ -8,9 +8,6 @@
--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%);
@ -37,7 +34,6 @@
--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,
@ -209,16 +205,15 @@ blockquote cite {
margin: -0.625rem -0.625rem 0.625rem -0.625rem;
}
a, span.f-bb-hashtag {
a {
color: var(--c-a-and-btn);
cursor: pointer;
text-decoration: none;
transition: color 0.5s, background-color 0.5s;
}
a:hover, a:focus, span.f-bb-hashtag:hover, span.f-bb-hashtag:focus {
color: var(--c-a-focus);
text-decoration: underline;
a:hover, a:focus {
color: var(--c-focus);
background-color: var(--bg-focus);
}
table {
@ -290,6 +285,7 @@ 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,8 +9,6 @@
--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%);
@ -261,7 +259,7 @@ body,
}
#fork select {
background-color: var(--bg-fprimary);
background-color: var(--bg-focus);
}
#fork select:not([multiple]) option {
@ -331,7 +329,7 @@ body,
color: var(--c-a-and-btn);
cursor: pointer;
text-decoration: none;
transition: color 0.5s, background-color 0.5s;
transition-duration: 1s;
}
#fork .f-btn {
@ -354,31 +352,18 @@ body,
}
#fork a:hover,
#fork a:focus {
color: var(--c-a-focus);
text-decoration: underline;
}
#fork a:focus-visible,
#fork .f-btn:focus-visible {
outline: 0.25rem double var(--c-focus-v);
text-decoration: none;
}
#fork a:focus,
#fork .f-btn:hover,
#fork .f-btn:focus,
#fork a.f-page:hover,
#fork a.f-page:focus {
#fork .f-btn:focus {
color: var(--c-focus);
background-color: var(--bg-focus);
text-decoration: none;
}
/*#fork a:active,
#fork a:active,
#fork .f-btn:active {
color: var(--c-active);
background-color: var(--bg-active);
}*/
}
#fork .f-inline > dt,
#fork .f-inline > dd {
@ -422,6 +407,7 @@ body,
background-color: var(--bg-like-nav);
}
/********/
/* Меню */
/********/
@ -487,18 +473,6 @@ 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;
@ -540,6 +514,17 @@ 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;
@ -970,6 +955,7 @@ body,
#fork-debug #id-fdebugtime {
padding: 0.3125rem 0.625rem;
text-align: center;
}
#fork-debug #fork-dgtable {
@ -1297,9 +1283,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;
@ -2741,6 +2727,17 @@ 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.73 Alpha Readme
# ForkBB rev.69 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/ustanovka-installation)
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)
### 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 (shared hosting):
1. Document Root != **public** folder:
* Rename **.dist.htaccess** to **.htaccess**,
* Rename **index.dist.php** to **index.php**.
2. Document Root == **public** folder (recommended if you have access to Apache configuration):
2. Document Root == **public** folder (recommended):
* Rename public/**.dist.htaccess** to public/**.htaccess**,
* Rename public/**index.dist.php** to public/**index.php**;
@ -43,8 +43,6 @@ Disallow: /reg
Disallow: /search
Disallow: /post
Disallow: /forum/scroll
Disallow: /forum/*/new/topic
Disallow: /topic/*/new/reply
Disallow: /userlist/*/DESC/
Disallow: /userlist/*/ASC/
```