Compare commits

..

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

44 changed files with 167 additions and 412 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

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

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

@ -55,7 +55,7 @@ class Func
public function __construct(protected Container $c)
{
$this->fUrl = $c->isInit('FRIENDLY_URL') ? $c->FRIENDLY_URL : [];
$this->fUrl = $this->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,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

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

@ -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);
}
}
@ -101,7 +101,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 +118,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);
}
}
@ -360,7 +360,7 @@ class Forum extends DataModel
}
}
$attr = $this->manager->create([
$attr = $this->c->forums->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->setManager($this)->setModelAttrs($attrs);
return $this->c->ForumModel->setModelAttrs($attrs);
}
/**

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

@ -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([
@ -367,7 +366,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|regex:%^(?:https?://.+)?$%', //???? это поле может быть отключено в форме
'redirect_url' => 'string:trim|max:255', //???? это поле может быть отключено в форме
'no_sum_mess' => 'required|integer|in:0,1',
'perms.*.read_forum' => 'checkbox',
'perms.*.post_replies' => 'checkbox',

View file

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

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' => ! 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

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

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

@ -119,15 +119,3 @@ 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,15 +119,3 @@ 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">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

@ -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;
@ -1327,6 +1338,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

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