use iterable stacks

master
Jean-Christian Paul Denis 2023-05-01 15:48:06 +02:00
parent 8d32477761
commit 9f2f04673b
Signed by: JcDenis
GPG Key ID: 1B5B8C5B90B6C951
19 changed files with 290 additions and 92 deletions

View File

@ -42,7 +42,7 @@ class ActionDescriptor
/**
* Get descriptor properties.
*
* @return array<string,string> The properties
* @return array<string,mixed> The properties
*/
public function dump(): array
{

View File

@ -0,0 +1,79 @@
<?php
/**
* @brief Uninstaller, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis and Contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\Uninstaller;
use Countable;
use Iterator;
/**
* Cleaner actions cleaners stack.
*
* @implements Iterator<int,ActionDescriptor>
*/
class ActionsCleanersStack implements Countable, Iterator
{
/** @var array<int,ActionDescriptor> $stack The stack */
private array $stack = [];
public function exists(int $offset): bool
{
return isset($this->stack[$offset]);
}
public function get(int $offset): ?ActionDescriptor
{
return $this->stack[$offset] ?? null;
}
public function set(ActionDescriptor $value): void
{
$this->stack[] = $value;
}
public function unset(int $offset): void
{
unset($this->stack[$offset]);
}
public function rewind(): void
{
reset($this->stack);
}
public function current(): false|ActionDescriptor
{
return current($this->stack);
}
public function key(): ?int
{
return key($this->stack);
}
public function next(): void
{
next($this->stack);
}
public function valid(): bool
{
return key($this->stack) !== null;
}
public function count(): int
{
return count($this->stack);
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* @brief Uninstaller, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugin
*
* @author Jean-Christian Denis and Contributors
*
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace Dotclear\Plugin\Uninstaller;
use Countable;
use Iterator;
/**
* Cleaner actions stack.
*
* @implements Iterator<string,ActionsCleanersStack>
*/
class ActionsStack implements Countable, Iterator
{
/** @var array<string,ActionsCleanersStack> $stack The stack */
private array $stack = [];
public function exists(string $offset): bool
{
return isset($this->stack[$offset]);
}
public function get(string $offset): ActionsCleanersStack
{
if (!$this->exists($offset)) {
$this->set($offset, new ActionsCleanersStack());
}
return $this->stack[$offset];
}
public function set(string $offset, ActionsCleanersStack $value): void
{
$this->stack[$offset] = $value;
}
public function unset(string $offset): void
{
unset($this->stack[$offset]);
}
public function rewind(): void
{
reset($this->stack);
}
public function current(): false|ActionsCleanersStack
{
return current($this->stack);
}
public function key(): ?string
{
return key($this->stack);
}
public function next(): void
{
next($this->stack);
}
public function valid(): bool
{
return key($this->stack) !== null;
}
public function count(): int
{
return count($this->stack);
}
}

View File

@ -46,7 +46,7 @@ class Backend extends dcNsProcess
return '';
}
return empty(Uninstaller::instance()->loadModules([$define])->getUserActions($define->getId())) ? '' :
return !count(Uninstaller::instance()->loadModules([$define])->getUserActions($define->getId())) ? '' :
sprintf(
' <a href="%s" class="button delete uninstall_module_button">' . __('Uninstall') . '</a>',
dcCore::app()->adminurl?->get('admin.plugin.' . My::id(), ['type' => $define->get('type'), 'id' => $define->getId()])
@ -90,10 +90,10 @@ class Backend extends dcNsProcess
$done = [];
foreach ($uninstaller->getDirectActions($define->getId()) as $cleaner => $stack) {
foreach ($stack as $action) {
if ($uninstaller->execute($cleaner, $action['action'], $action['ns'])) {
$done[] = $action['success'];
if ($uninstaller->execute($cleaner, $action->id, $action->ns)) {
$done[] = $action->success;
} else {
dcCore::app()->error->add($action['error']);
dcCore::app()->error->add($action->error);
}
}
}

View File

@ -15,9 +15,9 @@ declare(strict_types=1);
namespace Dotclear\Plugin\Uninstaller\Cleaner;
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
TraitCleanerDir,
ValueDescriptor
};
@ -28,7 +28,7 @@ use Dotclear\Plugin\Uninstaller\{
* It allows modules to delete an entire sub folder
* of DC_TPL_CACHE directory path.
*/
class Caches extends AbstractCleaner
class Caches extends CleanerParent
{
use TraitCleanerDir;

View File

@ -21,9 +21,9 @@ use Dotclear\Database\Statement\{
SelectStatement
};
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
ValueDescriptor
};
@ -33,7 +33,7 @@ use Dotclear\Plugin\Uninstaller\{
* It allows modules to delete a "log_table"
* of Dotclear dcLog::LOG_TABLE_NAME database table.
*/
class Logs extends AbstractCleaner
class Logs extends CleanerParent
{
public function __construct()
{

View File

@ -15,9 +15,9 @@ declare(strict_types=1);
namespace Dotclear\Plugin\Uninstaller\Cleaner;
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
ValueDescriptor,
TraitCleanerDir
};
@ -27,7 +27,7 @@ use Dotclear\Plugin\Uninstaller\{
*
* It allows modules to delete their own folder.
*/
class Plugins extends AbstractCleaner
class Plugins extends CleanerParent
{
use TraitCleanerDir;

View File

@ -21,9 +21,9 @@ use Dotclear\Database\Statement\{
SelectStatement
};
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
ValueDescriptor
};
@ -33,7 +33,7 @@ use Dotclear\Plugin\Uninstaller\{
* It allows modules to delete for users or global a preference workspace.
* It also allows to pick-up specific preference id by using delete_related action.
*/
class Preferences extends AbstractCleaner
class Preferences extends CleanerParent
{
public function __construct()
{

View File

@ -21,9 +21,9 @@ use Dotclear\Database\Statement\{
SelectStatement
};
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
ValueDescriptor
};
@ -33,7 +33,7 @@ use Dotclear\Plugin\Uninstaller\{
* It allows modules to delete for blogs or global a settings namespace.
* It also allows to pick-up specific setting id by using delete_related action.
*/
class Settings extends AbstractCleaner
class Settings extends CleanerParent
{
public function __construct()
{

View File

@ -25,9 +25,9 @@ use Dotclear\Database\Statement\{
SelectStatement
};
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
ValueDescriptor
};
@ -36,7 +36,7 @@ use Dotclear\Plugin\Uninstaller\{
*
* It allows modules to delete or truncate a database table.
*/
class Tables extends AbstractCleaner
class Tables extends CleanerParent
{
public function __construct()
{

View File

@ -16,9 +16,9 @@ namespace Dotclear\Plugin\Uninstaller\Cleaner;
use dcCore;
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
ValueDescriptor,
TraitCleanerDir
};
@ -28,7 +28,7 @@ use Dotclear\Plugin\Uninstaller\{
*
* It allows modules to delete their own folder.
*/
class Themes extends AbstractCleaner
class Themes extends CleanerParent
{
use TraitCleanerDir;

View File

@ -15,9 +15,9 @@ declare(strict_types=1);
namespace Dotclear\Plugin\Uninstaller\Cleaner;
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
ActionDescriptor,
CleanerDescriptor,
CleanerParent,
ValueDescriptor,
TraitCleanerDir
};
@ -28,7 +28,7 @@ use Dotclear\Plugin\Uninstaller\{
* It allows modules to delete an entire sub folder
* of DC_VAR directory path.
*/
class Vars extends AbstractCleaner
class Vars extends CleanerParent
{
use TraitCleanerDir;

View File

@ -17,7 +17,7 @@ namespace Dotclear\Plugin\Uninstaller\Cleaner;
use dcCore;
use Dotclear\Database\Statement\SelectStatement;
use Dotclear\Plugin\Uninstaller\{
AbstractCleaner,
CleanerParent,
ActionDescriptor,
CleanerDescriptor,
ValueDescriptor
@ -29,7 +29,7 @@ use Dotclear\Plugin\Uninstaller\{
* It allows modules to delete their versions
* from Dotclear dcCore::VERSION_TABLE_NAME database table.
*/
class Versions extends AbstractCleaner
class Versions extends CleanerParent
{
public function __construct()
{

View File

@ -20,7 +20,7 @@ namespace Dotclear\Plugin\Uninstaller;
* Cleaner manages only one part of uninstall process.
* For exemple Settings, Caches, db, etc...
*/
abstract class AbstractCleaner
abstract class CleanerParent
{
/** @var string $id The cleaner Id */
public readonly string $id;

View File

@ -14,62 +14,82 @@ declare(strict_types=1);
namespace Dotclear\Plugin\Uninstaller;
use Countable;
use dcCore;
use Iterator;
use Exception;
/**
* The cleaners stack.
*
* @implements Iterator<string,CleanerParent>
*/
class Cleaners
class CleanersStack implements Countable, Iterator
{
/** @var array<string,AbstractCleaner> $cleaners The cleaner stack */
private array $cleaners = [];
/** @var array<string,CleanerParent> $stack The cleaner stack */
private array $stack = [];
/**
* Contructor load cleaners.
*/
public function __construct()
{
# --BEHAVIOR-- UninstallerCleanersConstruct: Cleaners
# --BEHAVIOR-- UninstallerCleanersConstruct: CleanersStack
dcCore::app()->callBehavior('UninstallerCleanersConstruct', $this);
}
/**
* Add a cleaner.
*
* @param AbstractCleaner $cleaner The cleaner instance
*
* @return Cleaners Self instance
*/
public function add(AbstractCleaner $cleaner): Cleaners
public function exists(string $offset): bool
{
if (!isset($this->cleaners[$cleaner->id])) {
$this->cleaners[$cleaner->id] = $cleaner;
return isset($this->stack[$offset]);
}
public function get(string $offset): ?CleanerParent
{
return $this->stack[$offset] ?? null;
}
public function set(CleanerParent $value): CleanersStack
{
if (!isset($this->stack[$value->id])) {
$this->stack[$value->id] = $value;
}
return $this;
}
/**
* Get all clearners.
*
* @return array<string,AbstractCleaner> The cleaners
*/
public function dump(): array
public function unset(string $offset): void
{
return $this->cleaners;
unset($this->stack[$offset]);
}
/**
* Get a cleaner.
*
* @param string $id The cleaner id
*
* @return null|AbstractCleaner The cleaner
*/
public function get(string $id): ?AbstractCleaner
public function rewind(): void
{
return $this->cleaners[$id] ?? null;
reset($this->stack);
}
public function current(): false|CleanerParent
{
return current($this->stack);
}
public function key(): ?string
{
return key($this->stack);
}
public function next(): void
{
next($this->stack);
}
public function valid(): bool
{
return key($this->stack) !== null;
}
public function count(): int
{
return count($this->stack);
}
/**
@ -83,7 +103,7 @@ class Cleaners
*/
public function execute(string $id, string $action, string $ns): bool
{
if (!isset($this->cleaners[$id])) {
if (!isset($this->stack[$id])) {
throw new Exception(sprintf(__('Unknown cleaner "%s"'), $id));
}
if (in_array($ns, [My::id(), My::root()])) {
@ -93,6 +113,6 @@ class Cleaners
# --BEHAVIOR-- UninstallerBeforeAction: string, string, string
dcCore::app()->callBehavior('UninstallerBeforeAction', $id, $action, $ns);
return $this->cleaners[$id]->execute($action, $ns);
return $this->stack[$id]->execute($action, $ns);
}
}

View File

@ -25,6 +25,7 @@ use Dotclear\Helper\Html\Form\{
Form,
Hidden,
Label,
Link,
Para,
Submit,
Text
@ -69,7 +70,7 @@ class Manage extends dcNsProcess
// load uninstaller for selected module and check if it has action
$uninstaller = Uninstaller::instance()->loadModules([$define]);
$actions = $uninstaller->getUserActions($define->getId());
if (empty($actions)) {
if (!count($actions)) {
dcCore::app()->error->add(__('There are no uninstall actions for this module'));
self::doRedirect();
}
@ -151,12 +152,12 @@ class Manage extends dcNsProcess
}
// submit
$fields[] = (new Para())->items([
$fields[] = (new Para())->separator(' ')->items([
dcCore::app()->formNonce(false),
(new Hidden(['type'], self::getType())),
(new Hidden(['id'], $define->getId())),
(new Submit(['do']))->value(__('Perform selected actions'))->class('delete'),
(new Text('', ' <a class="button" href="' . self::getRedirect() . '">' . __('Cancel') . '</a>')),
(new Link())->class('button')->text(__('Cancel'))->href(self::getRedirect()),
]);
// display form

View File

@ -35,17 +35,17 @@ class Prepend extends dcNsProcess
}
// Add cleaners to Uninstaller
dcCore::app()->addBehavior('UninstallerCleanersConstruct', function (Cleaners $cleaners): void {
dcCore::app()->addBehavior('UninstallerCleanersConstruct', function (CleanersStack $cleaners): void {
$cleaners
->add(new Cleaner\Settings())
->add(new Cleaner\Preferences())
->add(new Cleaner\Tables())
->add(new Cleaner\Versions())
->add(new Cleaner\Logs())
->add(new Cleaner\Caches())
->add(new Cleaner\Vars())
->add(new Cleaner\Themes())
->add(new Cleaner\Plugins())
->set(new Cleaner\Settings())
->set(new Cleaner\Preferences())
->set(new Cleaner\Tables())
->set(new Cleaner\Versions())
->set(new Cleaner\Logs())
->set(new Cleaner\Caches())
->set(new Cleaner\Vars())
->set(new Cleaner\Themes())
->set(new Cleaner\Plugins())
;
});

View File

@ -29,8 +29,8 @@ class Uninstaller
/** @var string The Uninstall class name */
public const UNINSTALL_CLASS_NAME = 'Uninstall';
/** @var Cleaners $cleaners The cleaners stack */
public readonly Cleaners $cleaners;
/** @var CleanersStack $cleaners The cleaners stack */
public readonly CleanersStack $cleaners;
/** @var null|dcModuleDefine $module Current module */
private ?dcModuleDefine $module = null;
@ -41,8 +41,11 @@ class Uninstaller
/** @var array<int,string> List of modules with custom actions render */
private array $renders = [];
/** @var array List of registered actions */
private array $actions = ['user' => [], 'direct' => []];
/** @var array<string,ActionsStack> List of registered user actions */
private array $user_actions = [];
/** @var array<string,ActionsStack> List of registered direct actions */
private array $direct_actions = [];
/** @var Uninstaller $uninstaller Uninstaller instance */
private static $uninstaller;
@ -52,7 +55,7 @@ class Uninstaller
*/
public function __construct()
{
$this->cleaners = new Cleaners();
$this->cleaners = new CleanersStack();
}
/**
@ -81,10 +84,11 @@ class Uninstaller
public function loadModules(array $modules): Uninstaller
{
// reset unsintaller
$this->module = null;
$this->modules = [];
$this->renders = [];
$this->actions = ['user' => [], 'direct' => []];
$this->module = null;
$this->modules = [];
$this->renders = [];
$this->user_actions = [];
$this->direct_actions = [];
foreach ($modules as $module) {
if (!($module instanceof dcModuleDefine)) {
@ -138,7 +142,12 @@ class Uninstaller
*/
public function addUserAction(string $cleaner, string $action, string $ns): Uninstaller
{
$this->addAction('user', $cleaner, $action, $ns);
if (null !== $this->module && null !== ($res = $this->addAction($cleaner, $action, $ns))) {
if (!isset($this->user_actions[$this->module->getId()])) {
$this->user_actions[$this->module->getId()] = new ActionsStack();
}
$this->user_actions[$this->module->getId()]->get($cleaner)->set($res);
}
return $this;
}
@ -159,7 +168,12 @@ class Uninstaller
*/
public function addDirectAction(string $cleaner, string $action, string $ns): Uninstaller
{
$this->addAction('direct', $cleaner, $action, $ns);
if (null !== $this->module && null !== ($res = $this->addAction($cleaner, $action, $ns))) {
if (!isset($this->direct_actions[$this->module->getId()])) {
$this->direct_actions[$this->module->getId()] = new ActionsStack();
}
$this->direct_actions[$this->module->getId()]->get($cleaner)->set($res);
}
return $this;
}
@ -169,11 +183,11 @@ class Uninstaller
*
* @param string $id The module ID
*
* @return array List module user actions group by cleaner
* @return ActionsStack List module user actions group by cleaner
*/
public function getUserActions(string $id): array
public function getUserActions(string $id): ActionsStack
{
return $this->actions['user'][$id] ?? [];
return $this->user_actions[$id] ?? new ActionsStack();
}
/**
@ -181,11 +195,11 @@ class Uninstaller
*
* @param string $id The module ID
*
* @return array List module direct actions group by cleaner
* @return ActionsStack List module direct actions group by cleaner
*/
public function getDirectActions(string $id): array
public function getDirectActions(string $id): ActionsStack
{
return $this->actions['direct'][$id] ?? [];
return $this->direct_actions[$id] ?? new ActionsStack();
}
/**
@ -240,12 +254,13 @@ class Uninstaller
/**
* Add a predefined action to unsintall features.
*
* @param string $group The group (user or direct)
* @param string $cleaner The cleaner ID
* @param string $action The action ID
* @param string $ns Name of setting related to module.
*
* @return null|ActionDescriptor The action description
*/
private function addAction(string $group, string $cleaner, string $action, string $ns): void
private function addAction(string $cleaner, string $action, string $ns): ?ActionDescriptor
{
// no current module or no cleaner id or no ns or unknown cleaner action
if (null === $this->module
@ -253,11 +268,11 @@ class Uninstaller
|| empty($ns)
|| !isset($this->cleaners->get($cleaner)->actions[$action])
) {
return;
return null;
}
// fill action properties
$this->actions[$group][$this->module->getId()][$cleaner][] = new ActionDescriptor(
return new ActionDescriptor(
id: $action,
ns: $ns,
select: $this->cleaners->get($cleaner)->actions[$action]->select,

View File

@ -17,8 +17,8 @@ namespace Dotclear\Plugin\Uninstaller;
/**
* Cleaner value descriptor.
*
* Description of a value from AbstractCleaner::value()
* and AbstractCleaner::related()
* Description of a value from CleanerParent::value()
* and CleanerParent::related()
*/
class ValueDescriptor
{