Another teaspoon of code for the extension system 2

This commit is contained in:
Visman 2023-10-22 18:45:08 +07:00
parent dff71bcace
commit b3afd2b87f
9 changed files with 409 additions and 26 deletions
.gitignore
app
Models
config/ext
lang
templates/_default/layouts
public/style/ForkBB

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

@ -29,6 +29,8 @@ class Extension extends Model
*/
protected string $cKey = 'Extension';
protected array $prepareData;
protected function getdispalyName(): string
{
return $this->dbData['extra']['display-name'] ?? $this->fileData['extra']['display-name'];
@ -137,4 +139,46 @@ class Extension extends Model
{
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);
$this->prepareData['templates']['pre'][$cur['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

@ -25,13 +25,36 @@ class Extensions extends Manager
*/
protected string $cKey = 'Extensions';
/**
* Список отсканированных папок
*/
protected array $folders = [];
/**
* Текст ошибки
*/
protected string|array $error = '';
/**
* Путь до файла, который содержит данные из всех установленных расширений
*/
protected string $commonFile;
/**
* Возвращает 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->fromDB();
$list = $this->scan($this->c->DIR_EXT);
@ -110,24 +133,30 @@ class Extensions extends Manager
$v = $v->reset()
->addValidators([
])->addRules([
'name' => 'required|string',
'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' => 'required|array',
'autoload.psr-4.*' => 'required|string',
'require' => 'required|array',
'extra' => 'required|array',
'extra.display-name' => 'required|string',
'extra.requirements' => 'array',
'name' => 'required|string',
'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' => 'required|array',
'autoload.psr-4.*' => 'required|string',
'require' => 'required|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([
@ -170,4 +199,254 @@ class Extensions extends Manager
}
}
}
/**
* Устанавливает расширение
*/
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)';
$this->c->DB->exec($query, $vars);
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => 1,
'dbData' => $ext->fileData,
'fileData' => $ext->fileData,
]);
$this->updateCommon($ext);
return true;
}
/**
* Удаляет расширение
*/
public function uninstall(Extension $ext): bool
{
if (true !== $ext->canUninstall) {
$this->error = 'Invalid action';
return false;
}
$vars = [
':name' => $ext->name,
];
$query = 'DELETE
FROM ::extensions
WHERE ext_name=?s:name';
$this->c->DB->exec($query, $vars);
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => null,
'dbData' => null,
'fileData' => $ext->fileData,
]);
$this->updateCommon($ext);
return true;
}
/**
* Обновляет расширение
*/
public function update(Extension $ext): bool
{
if (true !== $ext->canUpdate) {
$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 = 'UPDATE ::extensions SET ext_data=?s:data
WHERE ext_name=?s:name';
$this->c->DB->exec($query, $vars);
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => $ext->dbStatus,
'dbData' => $ext->fileData,
'fileData' => $ext->fileData,
]);
$this->updateCommon($ext);
return true;
}
/**
* Обновляет расширение
*/
public function downdate(Extension $ext): bool
{
if (true !== $ext->canDowndate) {
$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 = 'UPDATE ::extensions SET ext_data=?s:data
WHERE ext_name=?s:name';
$this->c->DB->exec($query, $vars);
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => $ext->dbStatus,
'dbData' => $ext->fileData,
'fileData' => $ext->fileData,
]);
$this->updateCommon($ext);
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';
$this->c->DB->exec($query, $vars);
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => 1,
'dbData' => $ext->dbData,
'fileData' => $ext->fileData,
]);
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';
$this->c->DB->exec($query, $vars);
$ext->setModelAttrs([
'name' => $ext->name,
'dbStatus' => 0,
'dbData' => $ext->dbData,
'fileData' => $ext->fileData,
]);
return true;
}
/**
* Обновляет файл с общими данными по расширениям
*/
protected function updateCommon(Extension $ext): bool
{
if (\is_file($this->commonFile)) {
$data = include $this->commonFile;
} else {
$data = [];
}
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;
}
}
}

View file

@ -77,13 +77,10 @@ class Extensions extends Admin
return $this->c->Message->message('Invalid action');
}
$property = 'can' . \ucfirst($action);
if (true !== $ext->{$property}) {
return $this->c->Message->message('Invalid action');
if (true !== $this->c->extensions->{$action}($ext)) {
return $this->c->Message->message($this->c->extensions->error);
}
exit(var_dump('<pre>', $_POST, '</pre>'));
return $this->c->Redirect->page('AdminExtensions')->message("Redirect {$action}", FORK_MESS_SUCC);
}
}

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

View file

@ -86,3 +86,30 @@ 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."

View file

@ -86,3 +86,30 @@ 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' не найден."

View file

@ -118,8 +118,15 @@
<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>

View file

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