Compare commits

...

39 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
44 changed files with 416 additions and 171 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

@ -104,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,
@ -111,15 +123,6 @@ class Routing
'RegLog:redirect',
'RegLogRedirect'
);
if ($user->isAdmin) {
$r->add(
$r::GET,
'/reglog/callback/{name}',
'RegLog:callback',
'RegLogCallback'
);
}
}
// просмотр разрешен
@ -636,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

@ -55,7 +55,7 @@ class Func
public function __construct(protected Container $c)
{
$this->fUrl = $this->c->FRIENDLY_URL;
$this->fUrl = $c->isInit('FRIENDLY_URL') ? $c->FRIENDLY_URL : [];
}
/**

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

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

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

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

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

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

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

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