Merge pull request #25 from forkbb/Extensions

Extensions
This commit is contained in:
Visman 2023-10-28 21:46:07 +07:00 committed by GitHub
commit da79516766
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1665 additions and 12 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
/app/config/main.php
/app/config/_*
/app/config/db/*
/app/config/ext/*
/app/cache/**/*.php
/app/cache/**/*.lock
/app/cache/**/*.tmp

View file

@ -838,6 +838,18 @@ class Routing
'AdminAntispam:view',
'AdminAntispam'
);
$r->add(
$r::GET,
'/admin/extensions',
'AdminExtensions:info',
'AdminExtensions'
);
$r->add(
$r::PST,
'/admin/extensions/action',
'AdminExtensions:action',
'AdminExtensionsAction'
);
}
$uri = $_SERVER['REQUEST_URI'];

View file

@ -171,7 +171,7 @@ class Compiler
declare(strict_types=1);
use function \ForkBB\{__, num, dt, size};
use function \ForkBB\{__, num, dt, size, url};
?>
EOD;

View file

@ -0,0 +1,191 @@
<?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\Extension;
use ForkBB\Models\Model;
use RuntimeException;
class Extension extends Model
{
const NOT_INSTALLED = 0;
const DISABLED = 4;
const DISABLED_DOWN = 5;
const DISABLED_UP = 6;
const ENABLED = 8;
const ENABLED_DOWN = 9;
const ENABLED_UP = 10;
const CRASH = 12;
/**
* Ключ модели для контейнера
*/
protected string $cKey = 'Extension';
protected array $prepareData;
protected function getdispalyName(): string
{
return $this->dbData['extra']['display-name'] ?? $this->fileData['extra']['display-name'];
}
protected function getversion(): string
{
return $this->dbData['version'] ?? $this->fileData['version'];
}
protected function getfileVersion(): string
{
return $this->fileData['version'] ?? '-';
}
protected function getname(): string
{
return $this->dbData['name'] ?? $this->fileData['name'];
}
protected function getid(): string
{
return 'ext-' . \trim(\preg_replace('%\W+%', '-', $this->name), '-');
}
protected function getdescription(): string
{
return $this->dbData['description'] ?? $this->fileData['description'];
}
protected function gettime(): ?string
{
return $this->dbData['time'] ?? $this->fileData['time'];
}
protected function gethomepage(): ?string
{
return $this->dbData['homepage'] ?? $this->fileData['homepage'];
}
protected function getlicense(): ?string
{
return $this->dbData['license'] ?? $this->fileData['license'];
}
protected function getrequirements(): array
{
return $this->dbData['extra']['requirements'] ?? $this->fileData['extra']['requirements'];
}
protected function getauthors(): array
{
return $this->dbData['authors'] ?? $this->fileData['authors'];
}
protected function getstatus(): int
{
if (null === $this->dbStatus) {
return self::NOT_INSTALLED;
} elseif (empty($this->fileData['version'])) {
return self::CRASH;
}
switch (
\version_compare($this->fileData['version'], $this->dbData['version'])
+ 4 * (1 === $this->dbStatus)
) {
case -1:
return self::DISABLED_DOWN;
case 0:
return self::DISABLED;
case 1:
return self::DISABLED_UP;
case 3:
return self::ENABLED_DOWN;
case 4:
return self::ENABLED;
case 5:
return self::ENABLED_UP;
default:
throw new RuntimeException("Error in {$this->name} extension status");
}
}
protected function getcanInstall(): bool
{
return self::NOT_INSTALLED === $this->status;
}
protected function getcanUninstall(): bool
{
return \in_array($this->status, [self::DISABLED, self::DISABLED_DOWN, self::DISABLED_UP], true);
}
protected function getcanUpdate(): bool
{
return \in_array($this->status, [self::DISABLED_UP, self::ENABLED_UP], true);
}
protected function getcanDowndate(): bool
{
return \in_array($this->status, [self::DISABLED_DOWN, self::ENABLED_DOWN], true);
}
protected function getcanEnable(): bool
{
return self::DISABLED === $this->status;
}
protected function getcanDisable(): bool
{
return \in_array($this->status, [self::ENABLED, self::ENABLED_DOWN, self::ENABLED_UP, self::CRASH], true);
}
public function prepare(): bool|string|array
{
$this->prepareData = [];
if ($this->fileData['extra']['templates']) {
foreach ($this->fileData['extra']['templates'] as $cur) {
switch($cur['type']) {
case 'pre':
if (empty($cur['name'])) {
return 'PRE name not found';
} elseif (empty($cur['file'])) {
return ['Template file \'%s\' not found', $cur['file']];
}
$path = $this->fileData['path'] . '/' . \ltrim($cur['file'], '\\/');
if (! \is_file($path)) {
return ['Template file \'%s\' not found', $cur['file']];
}
$data = \file_get_contents($path);
foreach (\explode(',', $cur['template']) as $template) {
$this->prepareData['templates']['pre'][$template][$cur['name']][] = [
'priority' => $cur['priority'] ?: 0,
'data' => $data,
];
}
break;
default:
return 'Invalid template type';
}
}
}
return true;
}
public function prepareData(): array
{
return $this->prepareData;
}
}

View file

@ -0,0 +1,547 @@
<?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\Extension;
use ForkBB\Models\Extension\Extension;
use ForkBB\Models\Manager;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
use RuntimeException;
class Extensions extends Manager
{
/**
* Ключ модели для контейнера
*/
protected string $cKey = 'Extensions';
/**
* Список отсканированных папок
*/
protected array $folders = [];
/**
* Текст ошибки
*/
protected string|array $error = '';
protected string $commonFile;
protected string $preFile;
/**
* Возвращает action (или свойство) по его имени
*/
public function __get(string $name): mixed
{
return 'error' === $name ? $this->error : parent::__get($name);
}
/**
* Инициализирует менеджер
*/
public function init(): Extensions
{
$this->commonFile = $this->c->DIR_CONFIG . '/ext/common.php';
$this->preFile = $this->c->DIR_CONFIG . '/ext/pre.php';
$this->fromDB();
$list = $this->scan($this->c->DIR_EXT);
$this->fromList($this->prepare($list));
\uasort($this->repository, function (Extension $a, Extension $b) {
return $a->dispalyName <=> $b->dispalyName;
});
return $this;
}
/**
* Загружает в репозиторий из БД список расширений
*/
protected function fromDB(): void
{
$query = 'SELECT ext_name, ext_status, ext_data
FROM ::extensions
ORDER BY ext_name';
$stmt = $this->c->DB->query($query);
while ($row = $stmt->fetch()) {
$model = $this->c->ExtensionModel->setModelAttrs([
'name' => $row['ext_name'],
'dbStatus' => $row['ext_status'],
'dbData' => \json_decode($row['ext_data'], true, 512, \JSON_THROW_ON_ERROR),
]);
$this->set($row['ext_name'], $model);
}
}
/**
* Заполняет массив данными из файлов composer.json
*/
protected function scan(string $folder, array $result = []): array
{
$folder = \rtrim($folder, '\\/');
if (
empty($folder)
|| ! \is_dir($folder)
) {
throw new RuntimeException("Not a directory: {$folder}");
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS)
);
$files = new RegexIterator($iterator, '%[\\\\/]composer\.json$%i', RegexIterator::MATCH);
foreach ($files as $file) {
$data = \file_get_contents($file->getPathname());
if (\is_string($data)) {
$data = \json_decode($data, true);
}
$result[$file->getPath()] = $data;
}
$this->folders[] = $folder;
return $result;
}
/**
* Подготавливает данные для моделей
*/
protected function prepare(array $files): array
{
$v = clone $this->c->Validator;
$v = $v->reset()
->addValidators([
])->addRules([
'name' => 'required|string|regex:%^[a-z0-9](?:[_.-]?[a-z0-9]+)*/[a-z0-9](?:[_.-]?[a-z0-9]+)*$%',
'type' => 'required|string|in:forkbb-extension',
'description' => 'required|string',
'homepage' => 'string',
'version' => 'required|string',
'time' => 'string',
'license' => 'string',
'authors' => 'required|array',
'authors.*.name' => 'required|string',
'authors.*.email' => 'string',
'authors.*.homepage' => 'string',
'authors.*.role' => 'string',
'autoload.psr-4' => 'array',
'autoload.psr-4.*' => 'required|string',
'require' => 'array',
'extra' => 'required|array',
'extra.display-name' => 'required|string',
'extra.requirements' => 'array',
'extra.templates' => 'array',
'extra.templates.*.type' => 'required|string|in:pre',
'extra.templates.*.template' => 'required|string',
'extra.templates.*.name' => 'string',
'extra.templates.*.priority' => 'integer',
'extra.templates.*.file' => 'string',
])->addAliases([
])->addArguments([
])->addMessages([
]);
$result = [];
foreach ($files as $path => $file) {
if (! \is_array($file)) {
continue;
} elseif (! $v->validation($file)) {
continue;
}
$data = $v->getData(true);
$data['path'] = $path;
$result[$v->name] = $data;
}
return $result;
}
/**
* Дополняет репозиторий данными из файлов composer.json
*/
protected function fromList(array $list): void
{
foreach ($list as $name => $data) {
$model = $this->get($name);
if (! $model instanceof Extension) {
$model = $this->c->ExtensionModel->setModelAttrs([
'name' => $name,
'fileData' => $data,
]);
$this->set($name, $model);
} else {
$model->setModelAttr('fileData', $data);
}
}
}
/**
* Устанавливает расширение
*/
public function install(Extension $ext): bool
{
if (true !== $ext->canInstall) {
$this->error = 'Invalid action';
return false;
}
$result = $ext->prepare();
if (true !== $result) {
$this->error = $result;
return false;
}
$vars = [
':name' => $ext->name,
':data' => \json_encode($ext->fileData, FORK_JSON_ENCODE),
];
$query = 'INSERT INTO ::extensions (ext_name, ext_status, ext_data)
VALUES(?s:name, 1, ?s:data)';
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => 1,
'dbData' => $ext->fileData,
'fileData' => $ext->fileData,
]);
if (true !== $this->updateCommon($ext)) {
$this->error = 'An error occurred in updateCommon';
return false;
}
$this->updateIndividual();
$this->c->DB->exec($query, $vars);
return true;
}
/**
* Удаляет расширение
*/
public function uninstall(Extension $ext): bool
{
if (true !== $ext->canUninstall) {
$this->error = 'Invalid action';
return false;
}
$oldStatus = $ext->dbStatus;
$vars = [
':name' => $ext->name,
];
$query = 'DELETE
FROM ::extensions
WHERE ext_name=?s:name';
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => null,
'dbData' => null,
'fileData' => $ext->fileData,
]);
if (true !== $this->updateCommon($ext)) {
$this->error = 'An error occurred in updateCommon';
return false;
}
if ($oldStatus) {
$this->updateIndividual();
}
$this->c->DB->exec($query, $vars);
return true;
}
/**
* Обновляет расширение
*/
public function update(Extension $ext): bool
{
if (true === $ext->canUpdate) {
return $this->updown($ext);
} else {
$this->error = 'Invalid action';
return false;
}
}
/**
* Обновляет расширение
*/
public function downdate(Extension $ext): bool
{
if (true === $ext->canDowndate) {
return $this->updown($ext);
} else {
$this->error = 'Invalid action';
return false;
}
}
protected function updown(Extension $ext): bool
{
$oldStatus = $ext->dbStatus;
$result = $ext->prepare();
if (true !== $result) {
$this->error = $result;
return false;
}
$vars = [
':name' => $ext->name,
':data' => \json_encode($ext->fileData, FORK_JSON_ENCODE),
];
$query = 'UPDATE ::extensions SET ext_data=?s:data
WHERE ext_name=?s:name';
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => $ext->dbStatus,
'dbData' => $ext->fileData,
'fileData' => $ext->fileData,
]);
if (true !== $this->updateCommon($ext)) {
$this->error = 'An error occurred in updateCommon';
return false;
}
if ($oldStatus) {
$this->updateIndividual();
}
$this->c->DB->exec($query, $vars);
return true;
}
/**
* Включает расширение
*/
public function enable(Extension $ext): bool
{
if (true !== $ext->canEnable) {
$this->error = 'Invalid action';
return false;
}
$vars = [
':name' => $ext->name,
];
$query = 'UPDATE ::extensions SET ext_status=1
WHERE ext_name=?s:name';
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => 1,
'dbData' => $ext->dbData,
'fileData' => $ext->fileData,
]);
$this->updateIndividual();
$this->c->DB->exec($query, $vars);
return true;
}
/**
* Выключает расширение
*/
public function disable(Extension $ext): bool
{
if (true !== $ext->canDisable) {
$this->error = 'Invalid action';
return false;
}
$vars = [
':name' => $ext->name,
];
$query = 'UPDATE ::extensions SET ext_status=0
WHERE ext_name=?s:name';
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => 0,
'dbData' => $ext->dbData,
'fileData' => $ext->fileData,
]);
$this->updateIndividual();
$this->c->DB->exec($query, $vars);
return true;
}
/**
* Возвращает данные из файла с общими данными по расширениям
*/
protected function loadDataFromFile(string $file): array
{
if (\is_file($file)) {
return include $file;
} else {
return [];
}
}
/**
* Обновляет файл с общими данными по расширениям
*/
protected function updateCommon(Extension $ext): bool
{
$data = $this->loadDataFromFile($this->commonFile);
if ($ext::NOT_INSTALLED === $ext->status) {
unset($data[$ext->name]);
} else {
$data[$ext->name] = $ext->prepareData();
}
return $this->putData($this->commonFile, $data);
}
/**
* Записывает данные в указанный файл
*/
protected function putData(string $file, mixed $data): bool
{
$content = "<?php\n\nreturn " . \var_export($data, true) . ";\n";
if (false === \file_put_contents($file, $content, \LOCK_EX)) {
return false;
} else {
if (\function_exists('\\opcache_invalidate')) {
\opcache_invalidate($file, true);
} elseif (\function_exists('\\apc_delete_file')) {
\apc_delete_file($file);
}
return true;
}
}
/**
* Обновляет индивидуальные файлы с данными по расширениям
*/
protected function updateIndividual(): bool
{
$oldPre = $this->loadDataFromFile($this->preFile);
$templates = [];
$commonData = $this->loadDataFromFile($this->commonFile);
$pre = [];
$newPre = [];
// выделение данных
foreach ($this->repository as $ext) {
if (1 !== $ext->dbStatus) {
continue;
}
if (isset($commonData[$ext->name]['templates']['pre'])) {
$pre = \array_merge_recursive($pre, $commonData[$ext->name]['templates']['pre']);
}
}
// PRE-данные шаблонов
foreach ($pre as $template => $names) {
$templates[$template] = $template;
foreach ($names as $name => $list) {
\uasort($list, function (array $a, array $b) {
return $b['priority'] <=> $a['priority'];
});
$result = '';
foreach ($list as $value) {
$result .= $value['data'];
}
$newPre[$template][$name] = $result;
}
}
$this->putData($this->preFile, $newPre);
// удаление скомпилированных шаблонов
foreach (\array_merge($this->diffPre($oldPre, $newPre), $this->diffPre($newPre, $oldPre)) as $template) {
$this->c->View->delete($template);
}
return true;
}
/**
* Вычисляет расхождение для PRE-данных
*/
protected function diffPre(array $a, array $b): array
{
$result = [];
foreach ($a as $template => $names) {
if (! isset($b[$template])) {
$result[$template] = $template;
continue;
}
foreach ($names as $name => $value) {
if (
! isset($b[$template][$name])
|| $value !== $b[$template][$name]
) {
$result[$template] = $template;
continue 2;
}
}
}
return $result;
}
}

View file

@ -82,6 +82,7 @@ abstract class Admin extends Page
'uploads' => [$r->link('AdminUploads'), 'Uploads'],
'antispam' => [$r->link('AdminAntispam'), 'Antispam'],
'logs' => [$r->link('AdminLogs'), 'Logs'],
'extensions' => [$r->link('AdminExtensions'), 'Extensions'],
'maintenance' => [$r->link('AdminMaintenance'), 'Maintenance'],
];
}

View file

@ -0,0 +1,84 @@
<?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\Admin;
use ForkBB\Models\Extension\Extension;
use ForkBB\Models\Page;
use ForkBB\Models\Pages\Admin;
use Throwable;
use function \ForkBB\__;
class Extensions extends Admin
{
/**
* Подготавливает данные для шаблона
*/
public function info(array $args, string $method): Page
{
$this->c->Lang->load('admin_extensions');
$this->nameTpl = 'admin/extensions';
$this->aIndex = 'extensions';
$this->extensions = $this->c->extensions->repository;
$this->actionLink = $this->c->Router->link('AdminExtensionsAction');
$this->formsToken = $this->c->Csrf->create('AdminExtensionsAction');
return $this;
}
public function action(array $args, string $method): Page
{
$this->c->Lang->load('admin_extensions');
$v = $this->c->Validator->reset()
->addRules([
'token' => 'token:AdminExtensionsAction',
'name' => 'required|string',
'confirm' => 'required|string|in:1',
'install' => 'string',
'uninstall' => 'string',
'update' => 'string',
'downdate' => 'string',
'enable' => 'string',
'disable' => 'string',
])->addAliases([
])->addMessages([
'confirm' => [FORK_MESS_WARN, 'No confirm redirect'],
])->addArguments([
]);
if (! $v->validation($_POST)) {
$message = $this->c->Message;
$message->fIswev = $v->getErrors();
return $message->message('');
}
$ext = $this->c->extensions->get($v->name);
if (! $ext instanceof Extension) {
return $this->c->Message->message('Extension not found');
}
$actions = $v->getData(false, ['token', 'name', 'confirm']);
$action = \array_key_first($actions);
if (empty($action)) {
return $this->c->Message->message('Invalid action');
}
if (true !== $this->c->extensions->{$action}($ext)) {
return $this->c->Message->message($this->c->extensions->error);
}
return $this->c->Redirect->page('AdminExtensions', ['#' => $ext->id])->message("Redirect {$action}", FORK_MESS_SUCC);
}
}

View file

@ -114,9 +114,12 @@ class Install extends Admin
$folders = [
$this->c->DIR_CONFIG,
$this->c->DIR_CONFIG . '/db',
$this->c->DIR_CONFIG . '/ext',
$this->c->DIR_CACHE,
$this->c->DIR_CACHE . '/polls',
$this->c->DIR_PUBLIC . '/img/avatars',
$this->c->DIR_PUBLIC . '/upload',
$this->c->DIR_LOG,
];
foreach ($folders as $folder) {
@ -805,6 +808,18 @@ class Install extends Admin
];
$this->c->DB->createTable('::config', $schema);
// extensions
$schema = [
'FIELDS' => [
'ext_name' => ['VARCHAR(190)', false, ''],
'ext_status' => ['TINYINT', false, 0],
'ext_data' => ['TEXT', false],
],
'PRIMARY KEY' => ['ext_name'],
'ENGINE' => $this->DBEngine,
];
$this->c->DB->createTable('::extensions', $schema);
// forum_perms
$schema = [
'FIELDS' => [

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 = 68;
const LATEST_REV_WITH_DB_CHANGES = 70;
const LOCK_NAME = 'lock_update';
const LOCK_TTL = 1800;
const CONFIG_FILE = 'main.php';
@ -921,4 +921,62 @@ class Update extends Admin
return null;
}
/**
* rev.69 to rev.70
*/
protected function stageNumber69(array $args): ?int
{
$coreConfig = new CoreConfig($this->configFile);
$coreConfig->add(
'shared=>%DIR_EXT%',
'\'%DIR_ROOT%/ext\'',
'%DIR_VIEWS%'
);
$coreConfig->add(
'multiple=>ExtensionModel',
'\\ForkBB\\Models\\Extension\\Extension::class',
'DBMapModel'
);
$coreConfig->add(
'multiple=>ExtensionManager',
'\\ForkBB\\Models\\Extension\\Extensions::class',
'ExtensionModel'
);
$coreConfig->add(
'shared=>extensions',
'\'@ExtensionManager:init\'',
'attachments'
);
$coreConfig->add(
'multiple=>AdminExtensions',
'\\ForkBB\\Models\\Pages\\Admin\\Extensions::class',
'AdminAntispam'
);
$coreConfig->add(
'shared=>View=>config=>preFile',
'\'%DIR_CONFIG%/ext/pre.php\''
);
$coreConfig->save();
// extensions
$schema = [
'FIELDS' => [
'ext_name' => ['VARCHAR(190)', false, ''],
'ext_status' => ['TINYINT', false, 0],
'ext_data' => ['TEXT', false],
],
'PRIMARY KEY' => ['ext_name'],
];
$this->c->DB->createTable('::extensions', $schema);
return null;
}
}

View file

@ -42,8 +42,7 @@ define('FORK_GEN_FEM', 2);
define('FORK_JSON_ENCODE', \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR);
require __DIR__ . '/../vendor/autoload.php';
$loader = require __DIR__ . '/../vendor/autoload.php';
$errorHandler = new ErrorHandler();
if (\is_file(__DIR__ . '/config/main.php')) {
@ -54,6 +53,8 @@ if (\is_file(__DIR__ . '/config/main.php')) {
throw new RuntimeException('Application is not configured');
}
$c->autoloader = $loader;
$errorHandler->setContainer($c);
require __DIR__ . '/functions.php';
@ -69,7 +70,7 @@ if (
$c->BASE_URL = \str_replace('https://', 'http://', $c->BASE_URL);
}
$c->FORK_REVISION = 69;
$c->FORK_REVISION = 70;
$c->START = $forkStart;
$c->PUBLIC_URL = $c->BASE_URL . $forkPublicPrefix;

0
app/config/ext/.gitkeep Normal file
View file

View file

@ -84,6 +84,7 @@ return [
'%DIR_LANG%' => '%DIR_APP%/lang',
'%DIR_LOG%' => '%DIR_APP%/log',
'%DIR_VIEWS%' => '%DIR_APP%/templates',
'%DIR_EXT%' => '%DIR_ROOT%/ext',
'DB' => [
'class' => \ForkBB\Core\DB::class,
@ -109,6 +110,7 @@ return [
'cache' => '%DIR_CACHE%',
'defaultDir' => '%DIR_VIEWS%/_default',
'userDir' => '%DIR_VIEWS%/_user',
'preFile' => '%DIR_CONFIG%/ext/pre.php',
],
],
'Router' => [
@ -185,6 +187,7 @@ return [
],
'providerUser' => \ForkBB\Models\ProviderUser\ProviderUser::class,
'attachments' => \ForkBB\Models\Attachment\Attachments::class,
'extensions' => '@ExtensionManager:init',
'Csrf' => [
'class' => \ForkBB\Core\Csrf::class,
@ -409,6 +412,7 @@ return [
'AdminLogs' => \ForkBB\Models\Pages\Admin\Logs::class,
'AdminUploads' => \ForkBB\Models\Pages\Admin\Uploads::class,
'AdminAntispam' => \ForkBB\Models\Pages\Admin\Antispam::class,
'AdminExtensions' => \ForkBB\Models\Pages\Admin\Extensions::class,
'AdminListModel' => \ForkBB\Models\AdminList\AdminList::class,
'BanListModel' => \ForkBB\Models\BanList\BanList::class,
@ -417,6 +421,8 @@ return [
'CensorshipModel' => \ForkBB\Models\Censorship\Censorship::class,
'ConfigModel' => \ForkBB\Models\Config\Config::class,
'DBMapModel' => \ForkBB\Models\DBMap\DBMap::class,
'ExtensionModel' => \ForkBB\Models\Extension\Extension::class,
'ExtensionManager' => \ForkBB\Models\Extension\Extensions::class,
'ForumModel' => \ForkBB\Models\Forum\Forum::class,
'ForumManager' => \ForkBB\Models\Forum\Forums::class,
'GroupModel' => \ForkBB\Models\Group\Group::class,

View file

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

View file

@ -0,0 +1,118 @@
#
msgid ""
msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Project-Id-Version: ForkBB\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en\n"
msgid "Details"
msgstr "Details:"
msgid "Name"
msgstr "Name of package"
msgid "Description"
msgstr "Description"
msgid "Release date"
msgstr "Release date"
msgid "Homepage"
msgstr "Homepage"
msgid "Licence"
msgstr "Licence"
msgid "Requirements"
msgstr "Requirements:"
msgid "Authors"
msgstr "Author(s):"
msgid "php"
msgstr "PHP version"
msgid "forkbb"
msgstr "ForkBB revision"
msgid "Not installed"
msgstr "Not installed"
msgid "Disabled"
msgstr "Disabled"
msgid "Disabled, package changed"
msgstr "Disabled, package changed"
msgid "Enabled"
msgstr "Enabled"
msgid "Enabled, package changed"
msgstr "Enabled, but package changed!"
msgid "Crash"
msgstr "Crash, package not found!"
msgid "Install_"
msgstr "Install"
msgid "Uninstall_"
msgstr "Uninstall"
msgid "Enable_"
msgstr "Enable"
msgid "Disable_"
msgstr "Disable"
msgid "Update_"
msgstr "Update"
msgid "Downdate_"
msgstr "Downdate"
msgid "Package version"
msgstr "Package version"
msgid "Extension not found"
msgstr "Extension not found."
msgid "Invalid action"
msgstr "Invalid action."
msgid "Redirect install"
msgstr "The extension is installed."
msgid "Redirect uninstall"
msgstr "The extension has been uninstalled."
msgid "Redirect update"
msgstr "The extension has been updated."
msgid "Redirect downdate"
msgstr "The extension version has been downgraded."
msgid "Redirect enable"
msgstr "The extension is enabled."
msgid "Redirect disable"
msgstr "The extension is disabled."
msgid "Invalid template type"
msgstr "Invalid template type."
msgid "PRE name not found"
msgstr "PRE name not found."
msgid "Template file '%s' not found"
msgstr "Template file '%s' not found."
msgid "An error occurred in updateCommon"
msgstr "An error occurred in updateCommon."

View file

@ -86,3 +86,6 @@ msgstr "Антиспам"
msgid "Maintenance only"
msgstr "Доступно только в режиме обслуживания."
msgid "Extensions"
msgstr "Расширения"

View file

@ -0,0 +1,118 @@
#
msgid ""
msgstr ""
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Project-Id-Version: ForkBB\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
msgid "Details"
msgstr "Подробности:"
msgid "Name"
msgstr "Имя пакета"
msgid "Description"
msgstr "Описание"
msgid "Release date"
msgstr "Дата выпуска"
msgid "Homepage"
msgstr "Домашняя страница"
msgid "Licence"
msgstr "Лицензия"
msgid "Requirements"
msgstr "Требования:"
msgid "Authors"
msgstr "Автор(ы):"
msgid "php"
msgstr "Версия PHP"
msgid "forkbb"
msgstr "Ревизия ForkBB"
msgid "Not installed"
msgstr "Не установлено"
msgid "Disabled"
msgstr "Выключено"
msgid "Disabled, package changed"
msgstr "Выключено, пакет изменен"
msgid "Enabled"
msgstr "Включено"
msgid "Enabled, package changed"
msgstr "Включено, но пакет изменен!"
msgid "Crash"
msgstr "Сломано, пакет не найден!"
msgid "Install_"
msgstr "Установить"
msgid "Uninstall_"
msgstr "Удалить"
msgid "Enable_"
msgstr "Включить"
msgid "Disable_"
msgstr "Выключить"
msgid "Update_"
msgstr "Обновить"
msgid "Downdate_"
msgstr "Откатить"
msgid "Package version"
msgstr "Версия пакета"
msgid "Extension not found"
msgstr "Расширение не найдено."
msgid "Invalid action"
msgstr "Недопустимое действие."
msgid "Redirect install"
msgstr "Расширение установлено."
msgid "Redirect uninstall"
msgstr "Расширение деинсталлировано."
msgid "Redirect update"
msgstr "Расширение обновлено."
msgid "Redirect downdate"
msgstr "Версия расширения понижена."
msgid "Redirect enable"
msgstr "Расширение включено."
msgid "Redirect disable"
msgstr "Расширение выключено."
msgid "Invalid template type"
msgstr "Неверный тип шаблона."
msgid "PRE name not found"
msgstr "PRE-имя не найдено."
msgid "Template file '%s' not found"
msgstr "Файл шаблона '%s' не найден."
msgid "An error occurred in updateCommon"
msgstr "Возникла ошибка в updateCommon."

View file

@ -0,0 +1,148 @@
@extends ('layouts/admin')
@isset ($p->extensions)
<section id="fork-extsinfo" class="f-admin">
<h2>{!! __('Extensions') !!}</h2>
<div>
<fieldset>
<ol>
@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">
<span>{{ $ext->dispalyName }}</span>
-
<span>{{ $ext->version }}</span>
<span>/
@switch ($ext->status)
@case ($ext::NOT_INSTALLED)
{!! __('Not installed') !!}
@break
@case ($ext::DISABLED)
{!! __('Disabled') !!}
@break
@case ($ext::DISABLED_DOWN)
@case ($ext::DISABLED_UP)
{!! __('Disabled, package changed') !!}
@break
@case ($ext::ENABLED)
{!! __('Enabled') !!}
@break
@case ($ext::ENABLED_DOWN)
@case ($ext::ENABLED_UP)
{!! __('Enabled, package changed') !!}
@break
@case ($ext::CRASH)
{!! __('Crash') !!}
@break
@endswitch
/<span>
</summary>
<div class="f-extdata f-fdiv">
<form class="f-form" method="post" action="{{ $p->actionLink }}">
<fieldset class="f-extfs-details">
<legend class="f-fleg">{!! __('Details') !!}</legend>
<dl>
<dt>{!! __('Name') !!}</dt>
<dd>{{ $ext->name }}</dd>
</dl>
<dl>
<dt>{!! __('Package version') !!}</dt>
<dd>{{ $ext->fileVersion }}</dd>
</dl>
<dl>
<dt>{!! __('Description') !!}</dt>
<dd>{{ $ext->description }}</dd>
</dl>
@if ($ext->time)
<dl>
<dt>{!! __('Release date') !!}</dt>
<dd>{{ $ext->time }}</dd>
</dl>
@endif
@if ($ext->homepage)
<dl>
<dt>{!! __('Homepage') !!}</dt>
<dd><a href="{{ url($ext->homepage) }}">{{ $ext->homepage }}</a></dd>
</dl>
@endif
@if ($ext->license)
<dl>
<dt>{!! __('Licence') !!}</dt>
<dd>{{ $ext->license }}</dd>
</dl>
@endif
</fieldset>
<fieldset class="f-extfs-requirements">
<legend class="f-fleg">{!! __('Requirements') !!}</legend>
@foreach ($ext->requirements as $k => $v)
<dl>
<dt>{!! __($k) !!}</dt>
<dd>{{ $v }}</dd>
</dl>
@endforeach
</fieldset>
<fieldset class="f-extfs-authors">
<legend class="f-fleg">{!! __('Authors') !!}</legend>
@foreach ($ext->authors as $author)
<dl>
<dd class="f-extdd-author">
<span>{{ $author['name'] }}</span>
@if (! empty($author['email']) || ! empty($author['homepage']))
(
@if ($author['email'])
<a href="{{ url('mailto:'.$author['email']) }}">{{ $author['email'] }}</a>
@endif
@if ($author['homepage'])
@if ($author['email'])
|
@endif
<a href="{{ url($author['homepage']) }}">{{ $author['homepage'] }}</a>
@endif
)
@endif
@if ($author['role'])
[ {{ $author['role'] }} ]
@endif
</dd>
</dl>
@endforeach
</fieldset>
<fieldset calss="f-extfs-confirm">
<dl>
<dd>
<label class="f-flblch"><input name="confirm" class="f-ychk" type="checkbox" value="1">{!! __('Confirm action') !!}</label>
</dd>
</dl>
</fieldset>
<input type="hidden" name="name" value="{{ $ext->name }}">
<input type="hidden" name="token" value="{{ $p->formsToken }}">
<p class="f-btns">
@if ($ext->canInstall)
<button class="f-btn f-fbtn" name="install" value="install" title="{{ __('Install_') }}"><span>{!! __('Install_') !!}</span></button>
@endif
@if ($ext->canUninstall)
<button class="f-btn f-fbtn" name="uninstall" value="uninstall" title="{{ __('Uninstall_') }}"><span>{!! __('Uninstall_') !!}</span></button>
@endif
@if ($ext->canUpdate)
<button class="f-btn f-fbtn" name="update" value="update" title="{{ __('Update_') }}"><span>{!! __('Update_') !!}</span></button>
@endif
@if ($ext->canDowndate)
<button class="f-btn f-fbtn" name="downdate" value="downdate" title="{{ __('Downdate_') }}"><span>{!! __('Downdate_') !!}</span></button>
@endif
@if ($ext->canEnable)
<button class="f-btn f-fbtn" name="enable" value="enable" title="{{ __('Enable_') }}"><span>{!! __('Enable_') !!}</span></button>
@endif
@if ($ext->canDisable)
<button class="f-btn f-fbtn" name="disable" value="disable" title="{{ __('Disable_') }}"><span>{!! __('Disable_') !!}</span></button>
@endif
</p>
</form>
</div>
</details>
</li>
@endforeach
</ol>
</fieldset>
</div>
</section>
@endisset

View file

@ -1,7 +1,11 @@
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __('Info') !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE mainBefore -->
<div id="fork-ban" class="f-main">
@if ($p->bannedIp)
<p>{!! __('Your IP is blocked') !!}</p>
@ -17,3 +21,5 @@
@endif
<p>{!! __(['Ban message contact %s', $p->adminEmail]) !!}</p>
</div>
<!-- PRE mainAfter -->
<!-- PRE end -->

View file

@ -1,9 +1,15 @@
@extends ('layouts/main')
<!-- PRE start -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<section id="fork-changepass" class="f-main">
<!-- PRE mainStart -->
<div class="f-fdiv f-lrdiv">
<h2>{!! __('Change pass') !!}</h2>
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</section>
<!-- PRE mainAfter -->
@endif
<!-- PRE end -->

View file

@ -1,16 +1,26 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __($p->legend) !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<section id="fork-sendemail" class="f-post-form">
<!-- PRE mainStart -->
<h2>{!! __('Send email title') !!}</h2>
<div class="f-fdiv">
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</section>
<!-- PRE mainAfter -->
@endif
<!-- PRE end -->

View file

@ -21,6 +21,8 @@
@endif
@endsection
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
@if (\is_array($p->model->name))
<h1 id="fork-h1">{!! __($p->model->name) !!}</h1>
@ -31,10 +33,14 @@
<p class="f-fdesc">{!! $p->model->forum_desc !!}</p>
@endif
</div>
<!-- PRE h1After -->
@if ($forums = $p->model->subforums)
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
<!-- PRE subforumsBefore -->
<section id="fork-subforums">
<ol class="f-ftlist">
<li id="id-subforums{{ $p->model->id }}" class="f-category">
@ -50,7 +56,9 @@
</li>
</ol>
</section>
<!-- PRE subforumsAfter -->
@endif
<!-- PRE linksBBefore -->
<div class="f-nav-links">
@yield ('crumbs')
@if ($p->model->canCreateTopic || $p->model->pagination)
@ -66,7 +74,9 @@
</div>
@endif
</div>
<!-- PRE linksBAfter -->
@if ($p->topics)
<!-- PRE mainBefore -->
<section id="fork-forum" class="f-main">
<h2>{!! __('Topic list') !!}</h2>
<div class="f-ftlist">
@ -172,6 +182,8 @@
</ol>
</div>
</section>
<!-- PRE mainAfter -->
<!-- PRE linksABefore -->
<div class="f-nav-links">
@if ($p->model->canCreateTopic || $p->model->pagination || $p->model->canMarkRead || $p->model->canSubscription)
<div class="f-nlinks-a">
@ -201,12 +213,16 @@
@endif
@yield ('crumbs')
</div>
<!-- PRE linksAAfter -->
@endif
@if ($p->enableMod && $form = $p->formMod)
<!-- PRE modBefore -->
<aside id="fork-mod" class="f-moderate">
<h2>{!! __('Moderate') !!}</h2>
<div class="f-fdivm">
@include ('layouts/form')
</div>
</aside>
<!-- PRE modAfter -->
@endif
<!-- PRE end -->

View file

@ -1,8 +1,12 @@
@extends ('layouts/main')
<!-- PRE start -->
@if ($p->categoryes)
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __('Forum list') !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE mainBefore -->
<div class="f-main">
<ol class="f-ftlist">
@foreach ($p->categoryes as $id => $forums)
@ -20,7 +24,9 @@
@endforeach
</ol>
</div>
<!-- PRE mainAfter -->
@if ($p->linkMarkRead)
<!-- PRE linksBefore -->
<div class="f-nav-links">
<div class="f-nlinks">
<div class="f-actions-links">
@ -30,6 +36,10 @@
</div>
</div>
</div>
<!-- PRE linksAfter -->
@endif
@endif
<!-- PRE statsBefore -->
@include ('layouts/stats')
<!-- PRE statsafter -->
<!-- PRE end -->

View file

@ -1,12 +1,19 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __($p->adminHeader) !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links f-nav-admin{{ $p->mainSuffix or '' }}-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
<!-- PRE mainBefore -->
<div class="f-main f-main-admin{{ $p->mainSuffix or '' }}">
<!-- PRE menuBefore -->
<div id="fork-a-menu">
@if ($p->aNavigation)
<nav id="fork-a-nav" class="f-menu">
@ -20,7 +27,12 @@
</nav>
@endif
</div>
<!-- PRE menuAfter -->
<!-- PRE contentBefore -->
<div id="forka">
@yield ('content')
</div>
<!-- PRE contentAfter -->
</div>
<!-- PRE mainAfter -->
<!-- PRE end -->

View file

@ -1,7 +1,9 @@
@section ('crumbs')
<!-- PRE start -->
<nav class="f-nav-crumbs">
<ol class="f-crumbs" itemscope itemtype="https://schema.org/BreadcrumbList">
@foreach ($p->crumbs as $cur)
<!-- PRE foreachStart -->
@if (\is_object($cur[0]))
<li class="f-crumb @if ($cur[0]->is_subscribed) f-subscribed @endif" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"><!-- inline -->
<a class="f-crumb-a @if ($cur[2]) active" aria-current="page @endif" href="{{ $cur[0]->link }}" itemprop="item">
@ -27,7 +29,9 @@
<meta itemprop="position" content="{!! @iteration !!}">
</li><!-- endinline -->
@endif
<!-- PRE foreachEnd -->
@endforeach
</ol>
</nav>
<!-- PRE end -->
@endsection

View file

@ -1,4 +1,6 @@
<!-- PRE start -->
<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>
@if ($p->queries)
@ -23,4 +25,6 @@
</tbody>
</table>
@endif
<!-- PRE inEnd -->
</aside>
<!-- PRE end -->

View file

@ -1,5 +1,7 @@
<!-- PRE start -->
@if ($form['action'])
<form @if ($form['id']) id="{{ $form['id'] }}" @endif class="f-form" method="post" action="{{ $form['action'] }}" @if ($form['enctype']) enctype="{{ $form['enctype'] }}" @endif>
<!-- PRE formStart -->
@endif
@foreach ($form['sets'] as $setKey => $setVal)
@if ($setVal['inform'])
@ -31,6 +33,7 @@
</dt>
<dd>
@switch ($cur['type'])
<!-- PRE switchStart -->
@case ('text')
@case ('email')
@case ('number')
@ -97,6 +100,7 @@
</dd>
</dl>
@break
<!-- PRE switchEnd -->
@endswitch
@endforeach
</fieldset>
@ -116,12 +120,16 @@
@endif
<p class="f-btns">
@foreach ($form['btns'] as $key => $cur)
<!-- PRE btnsForeachStart -->
@if ('submit' === $cur['type'])
<button class="f-btn f-fbtn @if($cur['class']) {{ \implode(' ', $cur['class']) }} @endif" name="{{ $key }}" value="{{ $cur['value'] }}" @isset ($cur['accesskey']) accesskey="{{ $cur['accesskey'] }}" @endisset title="{{ $cur['value'] }}" @if ($cur['disabled']) disabled @endif><span>{{ $cur['value'] }}</span></button>
@elseif ('btn'=== $cur['type'])
<a class="f-btn f-fbtn @if($cur['class']) {{ \implode(' ', $cur['class']) }} @endif" data-name="{{ $key }}" href="{{ $cur['href'] }}" @isset ($cur['accesskey']) accesskey="{{ $cur['accesskey'] }}" @endisset title="{{ $cur['value'] }}"><span>{{ $cur['value'] }}</span></a>
@endif
<!-- PRE btnsForeachEnd -->
@endforeach
</p>
<!-- PRE formEnd -->
</form>
@endif
<!-- PRE end -->

View file

@ -1,4 +1,6 @@
<!-- PRE start -->
<aside class="f-iswev-wrap">
<!-- PRE inStart -->
@if ($iswev[FORK_MESS_INFO])
<div class="f-iswev f-info">
<p class="f-sim-header">Info message:</p>
@ -49,4 +51,6 @@
</ul>
</div>
@endif
<!-- PRE inEnd -->
</aside>
<!-- PRE end -->

View file

@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="{{ __('lang_identifier') }}" dir="{{ __('lang_direction') }}">
<head>
<!-- PRE headStart -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{! $p->pageTitle !}}</title>
@ -36,28 +37,39 @@
<{{ $pageHeader['type'] }} @foreach ($pageHeader['values'] as $key => $val) {{ $key }}="{{ $val }}" @endforeach>
@endif
@endforeach
<!-- PRE headEnd -->
</head>
<body>
<!-- PRE bodyStart -->
<div id="fork" class="@if ($p->fNavigation)f-with-nav @endif @if($p->fPMFlash) f-pm-flash @endif">
<!-- PRE headerBefore -->
<header id="fork-header">
<p id="id-fhth1"><a id="id-fhtha" rel="home" href="{{ $p->fRootLink }}">{{ $p->fTitle }}</a></p>
@if ('' != $p->fDescription)
<p id="id-fhtdesc">{!! $p->fDescription !!}</p>
@endif
</header>
<!-- PRE headerAfter -->
<!-- PRE mainBefore -->
<main id="fork-main">
@if ($p->fAnnounce)
<aside id="fork-announce">
<!-- PRE announceStart -->
<p class="f-sim-header">{!! __('Announcement') !!}</p>
<p id="id-facontent">{!! $p->fAnnounce !!}</p>
<!-- PRE announceEnd -->
</aside>
@endif
@if ($iswev = $p->fIswev)
@include ('layouts/iswev')
@endif
<!-- PRE contentBefore -->
@yield ('content')
<!-- PRE contentAfter -->
</main>
<!-- PRE mainAfter -->
@if ($p->fNavigation)
<!-- PRE navBefore -->
<nav id="fork-nav" class="f-menu @if ($p->fNavigation['search']) f-main-nav-search @endif">
<div id="fork-navdir">
<input id="id-mn-checkbox" class="f-menu-checkbox" type="checkbox">
@ -114,16 +126,27 @@
@endif
</div>
</nav>
<!-- PRE navAfter -->
@endif
<!-- PRE footerBefore -->
<footer id="fork-footer">
<p class="f-sim-header">{!! __('Board footer') !!}</p>
<div id="fork-footer-in">
<div></div>
<div><p id="id-fpoweredby">{!! __('Powered by') !!}</p></div>
<div>
<!-- PRE footerFirstStart -->
<!-- PRE footerFirstEnd -->
</div>
<div>
<!-- PRE footerSecondStart -->
<p id="id-fpoweredby">{!! __('Powered by') !!}</p>
<!-- PRE footerSecondEnd -->
</div>
</div>
<!-- debuginfo -->
</footer>
<!-- PRE footerAfter -->
</div>
<!-- PRE scriptsBefore -->
@foreach ($p->pageHeaders as $pageHeader)
@if ('script' === $pageHeader['type'])
@empty ($pageHeader['values']['inline'])
@ -133,5 +156,7 @@
@endempty
@endif
@endforeach
<!-- PRE scriptsAfter -->
<!-- PRE bodyEnd -->
</body>
</html>

View file

@ -1,12 +1,19 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __($p->pmHeader) !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links f-nav-pm-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
<!-- PRE mainBefore -->
<div class="f-main f-main-pm">
<!-- PRE menuBefore -->
<div id="fork-pm-menu">
@if ($p->pmNavigation)
<nav id="fork-pm-nav" class="f-menu">
@ -38,7 +45,12 @@
</nav>
@endif
</div>
<!-- PRE menuAfter -->
<!-- PRE contentBefore -->
<div id="forkpm">
@yield ('content')
</div>
<!-- PRE contentAfter -->
</div>
<!-- PRE mainAfter -->
<!-- PRE end -->

View file

@ -1,6 +1,9 @@
<!-- PRE start -->
<div class="f-post-poll">
<!-- PRE inStart -->
@if ($poll->canVote)
<form class="f-form" method="post" action="{{ $poll->link }}">
<!-- PRE formStart -->
<input type="hidden" name="token" value="{{ $poll->token }}">
@endif
@foreach ($poll->question as $q => $question)
@ -41,8 +44,11 @@
<p class="f-poll-btns">
<button class="f-btn" name="vote" value="{{ __('Vote') }}" title="{{ __('Vote') }}"><span>{!! __('Vote') !!}</span></button>
</p>
<!-- PRE formEnd -->
</form>
@elseif (null !== $poll->status)
<p class="f-poll-status"><span>{!! __($poll->status) !!}</span></p>
@endif
<!-- PRE inEnd -->
</div>
<!-- PRE end -->

View file

@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="{{ __('lang_identifier') }}" dir="{{ __('lang_direction') }}">
<head>
<!-- PRE headStart -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="{{ $p->timeout }}; URL={{ $p->link }}">
@ -16,9 +17,12 @@
<{{ $pageHeader['type'] }} @foreach ($pageHeader['values'] as $key => $val) {{ $key }}="{{ $val }}" @endforeach>
@endif
@endforeach
<!-- PRE headEnd -->
</head>
<body>
<!-- PRE bodyStart -->
<div id="fork">
<!-- PRE mainBefore -->
<main id="fork-main">
<aside id="fork-rdrct" class="f-main">
<h2 id="id-rdrct-h2">{!! __('Redirecting') !!}</h2>
@ -27,9 +31,13 @@
@endif
</aside>
</main>
<!-- PRE mainAfter -->
<!-- PRE footerBefore -->
<footer id="fork-footer">
<!-- debuginfo -->
</footer>
<!-- PRE footerAfter -->
</div>
<!-- PRE bodyEnd -->
</body>
</html>

View file

@ -1,4 +1,6 @@
<!-- PRE start -->
<aside id="fork-stats">
<!-- PRE inStart -->
<p class="f-sim-header">{!! __('Stats info') !!}</p>
@if ($p->stats)
<dl id="fork-stboard">
@ -36,4 +38,6 @@
@endforeach
</dl><!-- endinline -->
@endif
<!-- PRE inEnd -->
</aside>
<!-- PRE end -->

View file

@ -1,25 +1,39 @@
@extends ('layouts/main')
<!-- PRE start -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<section id="fork-login" class="f-main">
<!-- PRE mainStart -->
<div class="f-fdiv f-lrdiv">
<h2>{!! __('Login') !!}</h2>
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</section>
<!-- PRE mainAfter -->
@endif
@if ($form = $p->formOAuth)
<!-- PRE oauthBefore -->
<div id="fork-oauth" class="f-main">
<!-- PRE oauthStart -->
<div class="f-fdiv f-lrdiv">
@include ('layouts/form')
</div>
<!-- PRE oauthEnd -->
</div>
<!-- PRE oauthAfter -->
@endif
@if ($p->regLink)
<!-- PRE lgrgBefore -->
<div id="fork-lgrglnk" class="f-main">
<!-- PRE lgrgStart -->
<div class="f-fdiv f-lrdiv">
<div class="f-btns">
<a class="f-btn f-fbtn" href="{{ $p->regLink }}">{!! __('Not registered') !!}</a>
</div>
</div>
<!-- PRE lgrgEnd -->
</div>
<!-- PRE lgrgAfter -->
@endif
<!-- PRE end -->

View file

@ -1,4 +1,5 @@
@extends ('layouts/main')
<!-- PRE start -->
<div class="f-iswev-wrap">
<section class="f-iswev f-info">
<h2>Info message</h2>
@ -7,3 +8,4 @@
</ul>
</section>
</div>
<!-- PRE end -->

View file

@ -1,6 +1,8 @@
@extends ('layouts/main')
<!-- PRE start -->
@if ($p->back)
<div id="fork-bcklnk">
<p id="id-bcklnk-p"><a class="f-go-back" href="{{ $p->fRootLink }}">{!! __('Go back') !!}</a></p>
</div>
@endif
<!-- PRE end -->

View file

@ -1,15 +1,25 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __($p->formTitle) !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<div id="fork-modform" class="f-main">
<!-- PRE mainStart -->
<div class="f-fdiv">
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</div>
<!-- PRE mainAfter -->
@endif
<!-- PRE end -->

View file

@ -1,9 +1,15 @@
@extends ('layouts/main')
<!-- PRE start -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<section id="fork-resetpass" class="f-main">
<!-- PRE mainStart -->
<div class="f-fdiv f-lrdiv">
<h2>{!! __('Passphrase reset') !!}</h2>
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</section>
<!-- PRE mainAfter -->
@endif
<!-- PRE end -->

View file

@ -1,12 +1,18 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __($p->formTitle) !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
@if ($p->previewHtml)
<!-- PRE previewBefore -->
<section class="f-preview">
<h2>{!! __('Post preview') !!}</h2>
<div class="f-post-body">
@ -18,16 +24,22 @@
</div>
</div>
</section>
<!-- PRE previewAfter -->
@endif
@if ($form = $p->form)
<!-- PRE mainBefore -->
<section class="f-post-form">
<!-- PRE mainStart -->
<h2>{!! __($p->formTitle) !!}</h2>
<div class="f-fdiv">
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</section>
<!-- PRE mainAfter -->
@endif
@if ($p->posts)
<!-- PRE postsBefore -->
<section id="fork-view-posts">
<h2>{!! __($p->postsTitle) !!}</h2>
@foreach ($p->posts as $post)
@ -58,4 +70,6 @@
@endif
@endforeach
</section>
<!-- PRE postsAfter -->
@endif
<!-- PRE end -->

View file

@ -2,9 +2,13 @@
@section ('avatar')<img class="f-avatar-img" src="{{ $p->curUser->avatar }}" alt="{{ $p->curUser->username }}"> @endsection
@section ('signature') @if ($p->signatureSection){!! $p->curUser->htmlSign !!} @endif @endsection
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __(['%s\'s profile', $p->curUser->username]) !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
@if ($p->actionBtns)
@ -19,19 +23,29 @@
</div>
@endif
</div>
<!-- PRE linksAfter -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<section id="fork-profile{{ $p->profileIdSuffix or '' }}" class="f-main f-main-profile">
<!-- PRE mainStart -->
<h2>@if ($p->profileHeader === $p->curUser->username){{ $p->profileHeader }} @else{!! __($p->profileHeader) !!} @endif</h2>
<div class="f-fdiv">
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</section>
<!-- PRE mainAfter -->
@endif
@if ($form = $p->formOAuth)
<!-- PRE oauthBefore -->
<div id="fork-oauth" class="f-main">
<!-- PRE oauthStart -->
<div class="f-fdiv f-lrdiv">
<h2>{!! __('Add account') !!}</h2>
@include ('layouts/form')
</div>
<!-- PRE oauthEnd -->
</div>
<!-- PRE oauthAfter -->
@endif
<!-- PRE end -->

View file

@ -1,16 +1,26 @@
@extends ('layouts/main')
<!-- PRE start -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<section id="fork-reg" class="f-main">
<!-- PRE mainStart -->
<div class="f-fdiv f-lrdiv">
<h2>{!! __('Register') !!}</h2>
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</section>
<!-- PRE mainAfter -->
@endif
@if ($form = $p->formOAuth)
<!-- PRE oauthBefore -->
<div id="fork-oauth" class="f-main">
<!-- PRE oauthStart -->
<div class="f-fdiv f-lrdiv">
@include ('layouts/form')
</div>
<!-- PRE oauthEnd -->
</div>
<!-- PRE oauthAfter -->
@endif
<!-- PRE end -->

View file

@ -1,15 +1,25 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __('Report post') !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<div id="fork-report" class="f-post-form">
<!-- PRE mainStart -->
<div class="f-fdiv">
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</div>
<!-- PRE mainAfter -->
@endif
<!-- PRE end -->

View file

@ -1,18 +1,30 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __('Forum rules') !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
<!-- PRE mainBefore -->
<div id="fork-rules" class="f-main">
<div id="id-rules">{!! $p->rules !!}</div>
</div>
<!-- PRE mainAfter -->
@if ($form = $p->form)
<!-- PRE regBefore -->
<div id="fork-rconf" class="f-main">
<!-- PRE regStart -->
<div class="f-fdiv f-lrdiv">
@include ('layouts/form')
</div>
<!-- PRE regEnd -->
</div>
<!-- PRE regAfter -->
@endif
<!-- PRE end -->

View file

@ -1,15 +1,25 @@
@include ('layouts/crumbs')
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __('Search') !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBefore -->
<div class="f-nav-links">
@yield ('crumbs')
</div>
<!-- PRE linksAfter -->
@if ($form = $p->form)
<!-- PRE mainBefore -->
<div id="fork-search" class="f-main">
<!-- PRE mainStart -->
<div class="f-fdiv">
@include ('layouts/form')
</div>
<!-- PRE mainEnd -->
</div>
<!-- PRE mainAfter -->
@endif
<!-- PRE end -->

View file

@ -21,9 +21,13 @@
@endif
@endsection
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{{ $p->model->name }}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBBefore -->
<div class="f-nav-links">
@yield ('crumbs')
@if ($p->model->canReply || $p->model->closed || $p->model->pagination)
@ -45,6 +49,8 @@
</div>
@endif
</div>
<!-- PRE linksBAfter -->
<!-- PRE mainBefore -->
<section id="fork-topic" class="f-main">
<h2>{!! __('Post list') !!}</h2>
@foreach ($p->posts as $id => $post)
@ -166,6 +172,8 @@
@endif
@endforeach
</section>
<!-- PRE mainAfter -->
<!-- PRE linksABefore -->
<div class="f-nav-links">
@if ($p->model->canReply || $p->model->pagination || $p->model->canSubscription)
<div class="f-nlinks-a">
@ -191,22 +199,30 @@
@endif
@yield ('crumbs')
</div>
<!-- PRE linksAAfter -->
@if ($p->enableMod && $form = $p->formMod)
<!-- PRE modBefore -->
<aside id="fork-mod" class="f-moderate">
<h2>{!! __('Moderate') !!}</h2>
<div class="f-fdivm">
@include ('layouts/form')
</div>
</aside>
<!-- PRE modAfter -->
@endif
@if ($p->online)
<!-- PRE statsBefore -->
@include ('layouts/stats')
<!-- PRE statsAfter -->
@endif
@if ($form = $p->form)
<!-- PRE quickBefore -->
<section class="f-post-form">
<h2>{!! __('Quick post') !!}</h2>
<div class="f-fdiv">
@include ('layouts/form')
</div>
</section>
<!-- PRE quickAfter -->
@endif
<!-- PRE end -->

View file

@ -21,9 +21,13 @@
@endif
@endsection
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __($p->model->name) !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBBefore -->
<div class="f-nav-links">
@yield ('crumbs')
@if ($p->model->pagination)
@ -32,6 +36,8 @@
</div>
@endif
</div>
<!-- PRE linksBAfter -->
<!-- PRE mainBefore -->
<section id="fork-topic-ins" class="f-main">
<h2>{!! __('Post list') !!}</h2>
@foreach ($p->posts as $id => $post)
@ -95,6 +101,8 @@
@endif
@endforeach
</section>
<!-- PRE mainAfter -->
<!-- PRE linksABefore -->
<div class="f-nav-links">
@if ($p->model->pagination)
<div class="f-nlinks-a">
@ -103,3 +111,5 @@
@endif
@yield ('crumbs')
</div>
<!-- PRE linksAAfter -->
<!-- PRE end -->

View file

@ -21,9 +21,13 @@
@endif
@endsection
@extends ('layouts/main')
<!-- PRE start -->
<!-- PRE h1Before -->
<div class="f-mheader">
<h1 id="fork-h1">{!! __('User list') !!}</h1>
</div>
<!-- PRE h1After -->
<!-- PRE linksBBefore -->
<div class="f-nav-links">
@yield ('crumbs')
@if ($p->pagination)
@ -32,8 +36,10 @@
</div>
@endif
</div>
<!-- PRE linksBAfter -->
@if ($form = $p->form)
<section id="fork-usrlstform" class="f-main">
<!-- PRE searchBefore -->
<section id="fork-usrlstform" class="f-main">
<h2>{!! __($p->userRules->searchUsers ? 'User search head' : 'User sort head') !!}</h2>
<details>
<summary>{!! __($p->userRules->searchUsers ? 'User search head' : 'User sort head') !!}</summary>
@ -42,8 +48,10 @@
</div>
</details>
</section>
<!-- PRE searchAfter -->
@endif
@if ($p->userList)
<!-- PRE mainBefore -->
<section id="fork-usrlst" class="f-main">
<h2>{!! __('User_list') !!}</h2>
<div class="f-ulist">
@ -96,11 +104,15 @@
</ol>
</div>
</section>
<!-- PRE mainAfter -->
@if ($p->pagination)
<!-- PRE linksABefore -->
<div class="f-nav-links">
<div class="f-nlinks">
@yield ('pagination')
</div>
</div>
<!-- PRE linksAAfter -->
@endif
@endif
<!-- PRE end -->

0
ext/.gitkeep Normal file
View file

View file

@ -1,5 +1,5 @@
#forka {
--c-log-in-c: hsl(220,5%,12%);
--c-log-in-c: hsl(0,0%,0%);
}
/******************/
@ -110,6 +110,7 @@
#forka .f-fleg {
margin-bottom: 0.3125rem;
background: linear-gradient(var(--bg-like-nav), var(--bg-fprimary), var(--bg-like-nav));
}
#forka .f-flblch {
@ -410,7 +411,7 @@
/* Админка/Пользователи */
/************************/
#fork-ausersrch-rs .f-btns {
text-align: end;
justify-content: flex-end;
}
#fork-ausersrch-rs .f-btns .f-fbtn,
@ -1056,9 +1057,9 @@
text-align: center;
}
#fork #fork-logview summary.f-lgsu {
/*#fork #fork-logview summary.f-lgsu {
margin: 0;
}
}*/
#fork #fork-logview summary.f-lgsu::after {
content: none;
@ -1269,3 +1270,69 @@
width: 100%;
}
}
/****************************************/
/* Админка/РАсширения */
/****************************************/
#forka #fork-extsinfo summary.f-extsu::after {
content: none;
}
#fork-extsinfo .f-extli:first-child {
border-top: 0.0625rem dotted var(--br-fprimary);
}
#fork-extsinfo .f-extli {
border-bottom: 0.0625rem dotted var(--br-fprimary);
padding: 0.625rem;
}
#fork-extsinfo .f-extsu {
display: block;
}
#fork-extsinfo .f-extdata {
color: var(--c-fprimary);
border: 0.0625rem solid var(--br-fprimary);
padding: 0.3125rem;
}
#fork-extsinfo .f-extdd-author {
width: 100%;
}
#fork-extsinfo .f-ext-status0 {
color: var(--c-log-in-c);
background-color: #90CAF9;
}
#fork-extsinfo .f-ext-status4,
#fork-extsinfo .f-ext-status5,
#fork-extsinfo .f-ext-status6 {
color: var(--c-log-in-c);
background-color: #1976D2;
}
#fork-extsinfo .f-ext-status8 {
color: var(--c-log-in-c);
background-color: #4CAF50;
}
#fork-extsinfo .f-ext-status9,
#fork-extsinfo .f-ext-status10 {
color: var(--c-log-in-c);
background-color: #FF5722;
}
#fork-extsinfo .f-ext-status12 {
color: var(--c-log-in-c);
background-color: #D32F2F;
}
#forka #fork-extsinfo .f-fbtn {
width: auto;
}
#forka .f-fbtn[data-name="uninstall"]:not(.origin) {
color: red;
}

View file

@ -232,6 +232,9 @@ body,
#fork summary {
cursor: pointer;
}
#fork details[open] > summary {
margin-bottom: 0.625rem;
}