diff --git a/app/modules/web/Controllers/Plugin/PluginSearchBase.php b/app/modules/web/Controllers/Plugin/PluginSearchBase.php index 95d7208b..6f5c9047 100644 --- a/app/modules/web/Controllers/Plugin/PluginSearchBase.php +++ b/app/modules/web/Controllers/Plugin/PluginSearchBase.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -28,7 +28,7 @@ namespace SP\Modules\Web\Controllers\Plugin; use SP\Core\Application; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\QueryException; -use SP\Domain\Plugin\Ports\PluginServiceInterface; +use SP\Domain\Plugin\Ports\PluginManagerInterface; use SP\Html\DataGrid\DataGridInterface; use SP\Modules\Web\Controllers\ControllerBase; use SP\Modules\Web\Controllers\Helpers\Grid\PluginGrid; @@ -44,14 +44,14 @@ abstract class PluginSearchBase extends ControllerBase use ItemTrait; use JsonTrait; - private PluginServiceInterface $pluginService; + private PluginManagerInterface $pluginService; private PluginGrid $pluginGrid; public function __construct( - Application $application, - WebControllerHelper $webControllerHelper, - PluginServiceInterface $pluginService, - PluginGrid $pluginGrid + Application $application, + WebControllerHelper $webControllerHelper, + PluginManagerInterface $pluginService, + PluginGrid $pluginGrid ) { parent::__construct($application, $webControllerHelper); diff --git a/app/modules/web/Controllers/Plugin/ResetController.php b/app/modules/web/Controllers/Plugin/ResetController.php index be0ac4a8..87836758 100644 --- a/app/modules/web/Controllers/Plugin/ResetController.php +++ b/app/modules/web/Controllers/Plugin/ResetController.php @@ -30,8 +30,8 @@ use JsonException; use SP\Core\Application; use SP\Core\Events\Event; use SP\Core\Events\EventMessage; -use SP\Domain\Plugin\Ports\PluginDataServiceInterface; -use SP\Domain\Plugin\Ports\PluginServiceInterface; +use SP\Domain\Plugin\Ports\PluginDataInterface; +use SP\Domain\Plugin\Ports\PluginManagerInterface; use SP\Http\JsonMessage; use SP\Modules\Web\Controllers\ControllerBase; use SP\Modules\Web\Controllers\Traits\JsonTrait; @@ -44,14 +44,14 @@ final class ResetController extends ControllerBase { use JsonTrait; - private PluginServiceInterface $pluginService; - private PluginDataServiceInterface $pluginDataService; + private PluginManagerInterface $pluginService; + private PluginDataInterface $pluginDataService; public function __construct( - Application $application, - WebControllerHelper $webControllerHelper, - PluginServiceInterface $pluginService, - PluginDataServiceInterface $pluginDataService + Application $application, + WebControllerHelper $webControllerHelper, + PluginManagerInterface $pluginService, + PluginDataInterface $pluginDataService ) { parent::__construct($application, $webControllerHelper); diff --git a/lib/SP/DataModel/EncryptedModel.php b/lib/SP/DataModel/EncryptedModel.php index 84ff26bc..92e14483 100644 --- a/lib/SP/DataModel/EncryptedModel.php +++ b/lib/SP/DataModel/EncryptedModel.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2021, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -24,41 +24,36 @@ namespace SP\DataModel; - -use Defuse\Crypto\Exception\CryptoException; -use SP\Core\Crypt\Crypt; +use SP\Domain\Core\Crypt\CryptInterface; +use SP\Domain\Core\Exceptions\CryptException; use SP\Domain\Core\Exceptions\NoSuchPropertyException; /** * Trait EncryptedModel - * - * @package SP\DataModel */ trait EncryptedModel { - /** - * @var string - */ - private $key; + protected ?string $key = null; /** * @param string $key + * @param CryptInterface $crypt * @param string $property * - * @return static|null + * @return EncryptedModel + * @throws CryptException * @throws NoSuchPropertyException - * @throws CryptoException */ - public function encrypt(string $key, string $property = 'data') + public function encrypt(string $key, CryptInterface $crypt, string $property = 'data'): static { if (property_exists($this, $property)) { - if ($this->$property === null) { - return null; + if ($this->{$property} === null) { + return $this; } - $this->key = Crypt::makeSecuredKey($key); + $this->key = $crypt->makeSecuredKey($key); - $this->$property = Crypt::encrypt($this->$property, $this->key, $key); + $this->{$property} = $crypt->encrypt($this->{$property}, $this->key, $key); return $this; } @@ -68,22 +63,21 @@ trait EncryptedModel /** * @param string $key + * @param CryptInterface $crypt * @param string $property * - * @return static|null + * @return EncryptedModel + * @throws CryptException * @throws NoSuchPropertyException - * @throws CryptoException */ - public function decrypt(string $key, string $property = 'data') + public function decrypt(string $key, CryptInterface $crypt, string $property = 'data'): static { - if (property_exists($this, $property) - && !empty($this->key) - ) { - if ($this->$property === null) { - return null; + if (property_exists($this, $property) && !empty($this->key)) { + if ($this->{$property} === null) { + return $this; } - $this->$property = Crypt::decrypt($this->$property, $this->key, $key); + $this->{$property} = $crypt->decrypt($this->{$property}, $this->key, $key); return $this; } @@ -91,10 +85,7 @@ trait EncryptedModel throw new NoSuchPropertyException($property); } - /** - * @return string - */ - public function getKey(): string + public function getKey(): ?string { return $this->key; } diff --git a/lib/SP/Domain/Common/Adapters/HydratableInterface.php b/lib/SP/Domain/Common/Adapters/HydratableInterface.php index 417c4966..bacce3ae 100644 --- a/lib/SP/Domain/Common/Adapters/HydratableInterface.php +++ b/lib/SP/Domain/Common/Adapters/HydratableInterface.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -26,16 +26,15 @@ namespace SP\Domain\Common\Adapters; /** * Interface HydratableInterface - * - * @package SP\DataModel */ interface HydratableInterface { /** - * @param string|null $class - * @param string $property + * @template T + * @param class-string|null $class + * @param string $property * - * @return mixed|null + * @return T|null */ - public function hydrate(string $class = null, string $property = 'data'): mixed; + public function hydrate(?string $class = null, string $property = 'data'): mixed; } diff --git a/lib/SP/Domain/Common/Models/SerializedModel.php b/lib/SP/Domain/Common/Models/SerializedModel.php index fb62d19b..ce48ead1 100644 --- a/lib/SP/Domain/Common/Models/SerializedModel.php +++ b/lib/SP/Domain/Common/Models/SerializedModel.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -29,16 +29,15 @@ use SP\Util\Util; /** * Trait Datamodel - * - * @package SP\DataModel */ trait SerializedModel { /** - * @param string|null $class - * @param string $property + * @template T + * @param class-string|null $class + * @param string $property * - * @return mixed|null + * @return T|null * @throws NoSuchPropertyException */ public function hydrate(?string $class = null, string $property = 'data'): mixed diff --git a/lib/SP/Domain/Plugin/Ports/PluginCompatilityInterface.php b/lib/SP/Domain/Plugin/Ports/PluginCompatilityInterface.php new file mode 100644 index 00000000..a82a6a59 --- /dev/null +++ b/lib/SP/Domain/Plugin/Ports/PluginCompatilityInterface.php @@ -0,0 +1,45 @@ +. + */ + +namespace SP\Domain\Plugin\Ports; + +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; + +/** + * Class PluginCompatilityInterface + */ +interface PluginCompatilityInterface +{ + /** + * @param PluginInterface $plugin + * + * @return bool + * @throws ConstraintException + * @throws NoSuchItemException + * @throws QueryException + */ + public function checkFor(PluginInterface $plugin): bool; +} diff --git a/lib/SP/Domain/Plugin/Ports/PluginDataServiceInterface.php b/lib/SP/Domain/Plugin/Ports/PluginDataInterface.php similarity index 94% rename from lib/SP/Domain/Plugin/Ports/PluginDataServiceInterface.php rename to lib/SP/Domain/Plugin/Ports/PluginDataInterface.php index bbc285c0..7bec1b5c 100644 --- a/lib/SP/Domain/Plugin/Ports/PluginDataServiceInterface.php +++ b/lib/SP/Domain/Plugin/Ports/PluginDataInterface.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -34,11 +34,9 @@ use SP\Infrastructure\Database\QueryResult; use SP\Infrastructure\Plugin\Repositories\PluginDataModel; /** - * Class PluginDataService - * - * @package SP\Domain\Plugin\Services + * Class PluginDataInterface */ -interface PluginDataServiceInterface +interface PluginDataInterface { /** * Creates an item diff --git a/lib/SP/Plugin/PluginInterface.php b/lib/SP/Domain/Plugin/Ports/PluginInterface.php similarity index 63% rename from lib/SP/Plugin/PluginInterface.php rename to lib/SP/Domain/Plugin/Ports/PluginInterface.php index 04cfe0f1..aaf4c271 100644 --- a/lib/SP/Plugin/PluginInterface.php +++ b/lib/SP/Domain/Plugin/Ports/PluginInterface.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2021, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -22,22 +22,15 @@ * along with sysPass. If not, see . */ -namespace SP\Plugin; +namespace SP\Domain\Plugin\Ports; + +use SP\Domain\Core\Events\EventReceiver; /** * Interface PluginInterface - * - * @package SP\Plugin */ -interface PluginInterface extends PluginEventReceiver +interface PluginInterface extends EventReceiver { - /** - * Devuelve el tipo de plugin - * - * @return string|null - */ - public function getType(): ?string; - /** * Devuelve el directorio base del plugin * @@ -73,37 +66,13 @@ interface PluginInterface extends PluginEventReceiver */ public function getCompatibleVersion(): ?array; - /** - * Devuelve el nombre del plugin - * - * @return string|null - */ public function getName(): ?string; - /** - * @return mixed|null - */ - public function getData(); + public function getData(): mixed; + + public function saveData(int $id, mixed $data): void; - /** - * onLoad - */ public function onLoad(); - /** - * @return int - */ - public function getEnabled(); - - /** - * @param int $enabled - */ - public function setEnabled(int $enabled); - - /** - * @param string $version - * @param PluginOperation $pluginOperation - * @param mixed $extra - */ - public function upgrade(string $version, PluginOperation $pluginOperation, $extra = null); -} \ No newline at end of file + public function onUpgrade(string $version, PluginOperationInterface $pluginOperation, mixed $extra = null); +} diff --git a/lib/SP/Domain/Plugin/Ports/PluginLoaderInterface.php b/lib/SP/Domain/Plugin/Ports/PluginLoaderInterface.php new file mode 100644 index 00000000..82c93c67 --- /dev/null +++ b/lib/SP/Domain/Plugin/Ports/PluginLoaderInterface.php @@ -0,0 +1,33 @@ +. + */ + +namespace SP\Domain\Plugin\Ports; + +/** + * Interface PluginLoaderInterface + */ +interface PluginLoaderInterface +{ + public function loadFor(PluginInterface $plugin): void; +} diff --git a/lib/SP/Domain/Plugin/Ports/PluginServiceInterface.php b/lib/SP/Domain/Plugin/Ports/PluginManagerInterface.php similarity index 96% rename from lib/SP/Domain/Plugin/Ports/PluginServiceInterface.php rename to lib/SP/Domain/Plugin/Ports/PluginManagerInterface.php index 13315bbe..988e457a 100644 --- a/lib/SP/Domain/Plugin/Ports/PluginServiceInterface.php +++ b/lib/SP/Domain/Plugin/Ports/PluginManagerInterface.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -34,11 +34,9 @@ use SP\Infrastructure\Database\QueryResult; use SP\Infrastructure\Plugin\Repositories\PluginModel; /** - * Class PluginService - * - * @package SP\Domain\Plugin\Services + * Interface PluginManagerInterface */ -interface PluginServiceInterface +interface PluginManagerInterface { /** * Creates an item diff --git a/lib/SP/Domain/Plugin/Ports/PluginOperationInterface.php b/lib/SP/Domain/Plugin/Ports/PluginOperationInterface.php new file mode 100644 index 00000000..9a757eb1 --- /dev/null +++ b/lib/SP/Domain/Plugin/Ports/PluginOperationInterface.php @@ -0,0 +1,87 @@ +. + */ + +namespace SP\Domain\Plugin\Ports; + +use Defuse\Crypto\Exception\CryptoException; +use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\NoSuchPropertyException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; + +/** + * Interface PluginOperation + */ +interface PluginOperationInterface +{ + /** + * @param int $itemId + * @param mixed $data + * + * @return int + * @throws CryptoException + * @throws ConstraintException + * @throws NoSuchPropertyException + * @throws QueryException + * @throws ServiceException + */ + public function create(int $itemId, mixed $data): int; + + /** + * @param int $itemId + * @param mixed $data + * + * @return int + * @throws CryptoException + * @throws ConstraintException + * @throws NoSuchPropertyException + * @throws QueryException + * @throws ServiceException + */ + public function update(int $itemId, mixed $data): int; + + /** + * @throws ConstraintException + * @throws QueryException + * @throws NoSuchItemException + */ + public function delete(int $itemId): void; + + /** + * @template T + * + * @param int $itemId + * @param class-string|null $class + * + * @return T + * + * @throws ConstraintException + * @throws CryptoException + * @throws NoSuchPropertyException + * @throws QueryException + * @throws ServiceException + */ + public function get(int $itemId, ?string $class = null): mixed; +} diff --git a/lib/SP/Domain/Plugin/Ports/PluginRegisterInterface.php b/lib/SP/Domain/Plugin/Ports/PluginRegisterInterface.php new file mode 100644 index 00000000..e3b3f7b3 --- /dev/null +++ b/lib/SP/Domain/Plugin/Ports/PluginRegisterInterface.php @@ -0,0 +1,33 @@ +. + */ + +namespace SP\Domain\Plugin\Ports; + +/** + * Interface PluginRegisterInterface + */ +interface PluginRegisterInterface +{ + public function registerFor(PluginInterface $plugin): void; +} diff --git a/lib/SP/Domain/Plugin/Ports/PluginUpgraderInterface.php b/lib/SP/Domain/Plugin/Ports/PluginUpgraderInterface.php new file mode 100644 index 00000000..458ad2c3 --- /dev/null +++ b/lib/SP/Domain/Plugin/Ports/PluginUpgraderInterface.php @@ -0,0 +1,33 @@ +. + */ + +namespace SP\Domain\Plugin\Ports; + +/** + * Interface PluginUpgraderInterface + */ +interface PluginUpgraderInterface +{ + public function upgradeFor(PluginInterface $plugin, string $version): void; +} diff --git a/lib/SP/Domain/Plugin/Services/PluginCompatility.php b/lib/SP/Domain/Plugin/Services/PluginCompatility.php new file mode 100644 index 00000000..d4bcf92b --- /dev/null +++ b/lib/SP/Domain/Plugin/Services/PluginCompatility.php @@ -0,0 +1,112 @@ +. + */ + +namespace SP\Domain\Plugin\Services; + +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Common\Services\Service; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Domain\Install\Services\InstallerService; +use SP\Domain\Plugin\Ports\PluginCompatilityInterface; +use SP\Domain\Plugin\Ports\PluginInterface; +use SP\Domain\Plugin\Ports\PluginManagerInterface; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; + +use function SP\__; + +/** + * Class PluginCompatility + */ +final class PluginCompatility extends Service implements PluginCompatilityInterface +{ + + public function __construct( + Application $application, + private readonly PluginManagerInterface $pluginService + ) { + parent::__construct($application); + } + + /** + * @param PluginInterface $plugin + * + * @return bool + * @throws ConstraintException + * @throws NoSuchItemException + * @throws QueryException + */ + public function checkFor(PluginInterface $plugin): bool + { + $pluginVersion = implode('.', $plugin->getCompatibleVersion()); + $appVersion = implode('.', array_slice(InstallerService::VERSION, 0, 2)); + + if (version_compare($pluginVersion, $appVersion) === -1) { + $this->eventDispatcher->notify( + 'plugin.check.version', + new Event( + $this, + EventMessage::factory() + ->addDescription( + sprintf( + __('Plugin version not compatible (%s)'), + implode('.', $plugin->getVersion()) + ) + ) + ) + ); + + $this->pluginService->toggleEnabledByName($plugin->getName(), false); + + $this->eventDispatcher->notify( + 'plugin.edit.disable', + new Event( + $this, + EventMessage::factory() + ->addDetail(__('Plugin disabled'), $plugin->getName()) + ) + ); + + return false; + } + + $this->eventDispatcher->notify( + 'plugin.check.version', + new Event( + $this, + EventMessage::factory() + ->addDescription( + sprintf( + __('Plugin version compatible (%s)'), + implode('.', $plugin->getVersion()) + ) + ) + ) + ); + + return true; + } +} diff --git a/lib/SP/Domain/Plugin/Services/PluginDataService.php b/lib/SP/Domain/Plugin/Services/PluginData.php similarity index 79% rename from lib/SP/Domain/Plugin/Services/PluginDataService.php rename to lib/SP/Domain/Plugin/Services/PluginData.php index 5637e81b..50bf2e5e 100644 --- a/lib/SP/Domain/Plugin/Services/PluginDataService.php +++ b/lib/SP/Domain/Plugin/Services/PluginData.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -24,74 +24,81 @@ namespace SP\Domain\Plugin\Services; -use Defuse\Crypto\Exception\CryptoException; use SP\Core\Application; use SP\Domain\Common\Services\Service; use SP\Domain\Common\Services\ServiceException; +use SP\Domain\Core\Crypt\CryptInterface; use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\CryptException; use SP\Domain\Core\Exceptions\NoSuchPropertyException; use SP\Domain\Core\Exceptions\QueryException; use SP\Domain\Core\Exceptions\SPException; +use SP\Domain\Plugin\Ports\PluginDataInterface; use SP\Domain\Plugin\Ports\PluginDataRepositoryInterface; -use SP\Domain\Plugin\Ports\PluginDataServiceInterface; use SP\Infrastructure\Common\Repositories\NoSuchItemException; use SP\Infrastructure\Database\QueryResult; use SP\Infrastructure\Plugin\Repositories\PluginDataModel; -use SP\Infrastructure\Plugin\Repositories\PluginDataRepository; + +use function SP\__u; /** - * Class PluginDataService - * - * @package SP\Domain\Plugin\Services + * Class PluginData */ -final class PluginDataService extends Service implements PluginDataServiceInterface +final class PluginData extends Service implements PluginDataInterface { - private PluginDataRepository $pluginDataRepository; - - public function __construct(Application $application, PluginDataRepositoryInterface $pluginDataRepository) - { + public function __construct( + Application $application, + private readonly PluginDataRepositoryInterface $pluginDataRepository, + private readonly CryptInterface $crypt, + ) { parent::__construct($application); - - $this->pluginDataRepository = $pluginDataRepository; } /** * Creates an item * - * @throws CryptoException + * @param PluginDataModel $itemData + * @return QueryResult * @throws ConstraintException * @throws NoSuchPropertyException * @throws QueryException * @throws ServiceException + * @throws CryptException */ public function create(PluginDataModel $itemData): QueryResult { - return $this->pluginDataRepository->create($itemData->encrypt($this->getMasterKeyFromContext())); + return $this->pluginDataRepository->create($itemData->encrypt($this->getMasterKeyFromContext(), $this->crypt)); } /** * Updates an item * - * @throws CryptoException + * @param PluginDataModel $itemData + * @return int * @throws ConstraintException + * @throws CryptException * @throws NoSuchPropertyException * @throws QueryException * @throws ServiceException */ public function update(PluginDataModel $itemData): int { - return $this->pluginDataRepository->update($itemData->encrypt($this->getMasterKeyFromContext())); + return $this->pluginDataRepository->update($itemData->encrypt($this->getMasterKeyFromContext(), $this->crypt)); } /** * Returns the item for given plugin and id * - * @throws NoSuchItemException - * @throws CryptoException + * @param string $name + * @param int $id + * @return PluginDataModel * @throws ConstraintException + * @throws CryptException + * @throws NoSuchItemException * @throws NoSuchPropertyException * @throws QueryException + * @throws SPException * @throws ServiceException */ public function getByItemId(string $name, int $id): PluginDataModel @@ -105,18 +112,20 @@ final class PluginDataService extends Service implements PluginDataServiceInterf /** @var PluginDataModel $itemData */ $itemData = $result->getData(); - return $itemData->decrypt($this->getMasterKeyFromContext()); + return $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt); } /** * Returns the item for given id * + * @param string $id * @return PluginDataModel[] - * @throws CryptoException * @throws ConstraintException + * @throws CryptException + * @throws NoSuchItemException * @throws NoSuchPropertyException * @throws QueryException - * @throws NoSuchItemException + * @throws SPException * @throws ServiceException */ public function getById(string $id): array @@ -131,9 +140,8 @@ final class PluginDataService extends Service implements PluginDataServiceInterf array_walk( $data, - function ($itemData) { - /** @var PluginDataModel $itemData */ - $itemData->decrypt($this->getMasterKeyFromContext()); + function (PluginDataModel $itemData) { + $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt); } ); @@ -144,10 +152,11 @@ final class PluginDataService extends Service implements PluginDataServiceInterf * Returns all the items * * @return PluginDataModel[] - * @throws CryptoException * @throws ConstraintException + * @throws CryptException * @throws NoSuchPropertyException * @throws QueryException + * @throws SPException * @throws ServiceException */ public function getAll(): array @@ -158,7 +167,7 @@ final class PluginDataService extends Service implements PluginDataServiceInterf $data, function ($itemData) { /** @var PluginDataModel $itemData */ - $itemData->decrypt($this->getMasterKeyFromContext()); + $itemData->decrypt($this->getMasterKeyFromContext(), $this->crypt); } ); diff --git a/lib/SP/Domain/Plugin/Services/PluginLoader.php b/lib/SP/Domain/Plugin/Services/PluginLoader.php new file mode 100644 index 00000000..83fec6fc --- /dev/null +++ b/lib/SP/Domain/Plugin/Services/PluginLoader.php @@ -0,0 +1,93 @@ +. + */ + +namespace SP\Domain\Plugin\Services; + +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Common\Services\Service; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Domain\Plugin\Ports\PluginInterface; +use SP\Domain\Plugin\Ports\PluginLoaderInterface; +use SP\Domain\Plugin\Ports\PluginManagerInterface; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; + +use function SP\__; + +/** + * Class PluginLoader + */ +final class PluginLoader extends Service implements PluginLoaderInterface +{ + public function __construct(Application $application, private readonly PluginManagerInterface $pluginService) + { + parent::__construct($application); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function loadFor(PluginInterface $plugin): void + { + try { + $model = $this->pluginService->getByName($plugin->getName()); + } catch (NoSuchItemException $e) { + $this->eventDispatcher->notify( + 'plugin.load', + new Event( + $e, + EventMessage::factory() + ->addDetail(__('Plugin not registered'), $plugin->getName()) + ) + ); + + return; + } + + if ($model->getEnabled()) { + $this->eventDispatcher->attach($plugin); + + $this->eventDispatcher->notify( + 'plugin.load', + new Event( + $this, + EventMessage::factory() + ->addDetail(__('Plugin loaded'), $plugin->getName()) + ) + ); + } else { + $this->eventDispatcher->notify( + 'plugin.load', + new Event( + $this, + EventMessage::factory() + ->addDetail(__('Plugin not loaded (disabled)'), $plugin->getName()) + ) + ); + } + } +} diff --git a/lib/SP/Domain/Plugin/Services/PluginService.php b/lib/SP/Domain/Plugin/Services/PluginManager.php similarity index 93% rename from lib/SP/Domain/Plugin/Services/PluginService.php rename to lib/SP/Domain/Plugin/Services/PluginManager.php index 5ae8495d..ac7c3d09 100644 --- a/lib/SP/Domain/Plugin/Services/PluginService.php +++ b/lib/SP/Domain/Plugin/Services/PluginManager.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -32,19 +32,19 @@ use SP\Domain\Common\Services\ServiceException; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\QueryException; use SP\Domain\Core\Exceptions\SPException; +use SP\Domain\Plugin\Ports\PluginManagerInterface; use SP\Domain\Plugin\Ports\PluginRepositoryInterface; -use SP\Domain\Plugin\Ports\PluginServiceInterface; use SP\Infrastructure\Common\Repositories\NoSuchItemException; use SP\Infrastructure\Database\QueryResult; use SP\Infrastructure\Plugin\Repositories\PluginModel; use SP\Infrastructure\Plugin\Repositories\PluginRepository; +use function SP\__u; + /** - * Class PluginService - * - * @package SP\Domain\Plugin\Services + * Class PluginManager */ -final class PluginService extends Service implements PluginServiceInterface +final class PluginManager extends Service implements PluginManagerInterface { private PluginRepository $pluginRepository; @@ -80,9 +80,12 @@ final class PluginService extends Service implements PluginServiceInterface /** * Returns the item for given id * + * @param int $id + * @return PluginModel * @throws ConstraintException - * @throws QueryException * @throws NoSuchItemException + * @throws QueryException + * @throws SPException */ public function getById(int $id): PluginModel { @@ -101,6 +104,7 @@ final class PluginService extends Service implements PluginServiceInterface * @return PluginModel[] * @throws ConstraintException * @throws QueryException + * @throws SPException */ public function getAll(): array { @@ -110,11 +114,12 @@ final class PluginService extends Service implements PluginServiceInterface /** * Returns all the items for given ids * - * @param int[] $ids + * @param int[] $ids * * @return PluginModel[] * @throws ConstraintException * @throws QueryException + * @throws SPException */ public function getByIdBatch(array $ids): array { @@ -124,7 +129,7 @@ final class PluginService extends Service implements PluginServiceInterface /** * Deletes all the items for given ids * - * @param int[] $ids + * @param int[] $ids * * @throws SPException * @throws ConstraintException @@ -166,9 +171,12 @@ final class PluginService extends Service implements PluginServiceInterface /** * Devuelve los datos de un plugin por su nombre * - * @throws NoSuchItemException + * @param string $name + * @return PluginModel * @throws ConstraintException + * @throws NoSuchItemException * @throws QueryException + * @throws SPException */ public function getByName(string $name): PluginModel { @@ -259,6 +267,7 @@ final class PluginService extends Service implements PluginServiceInterface * @return ItemData[] * @throws ConstraintException * @throws QueryException + * @throws SPException */ public function getEnabled(): array { diff --git a/lib/SP/Plugin/PluginOperation.php b/lib/SP/Domain/Plugin/Services/PluginOperation.php similarity index 66% rename from lib/SP/Plugin/PluginOperation.php rename to lib/SP/Domain/Plugin/Services/PluginOperation.php index 5f826d4a..217c29bd 100644 --- a/lib/SP/Plugin/PluginOperation.php +++ b/lib/SP/Domain/Plugin/Services/PluginOperation.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -22,45 +22,31 @@ * along with sysPass. If not, see . */ -namespace SP\Plugin; +namespace SP\Domain\Plugin\Services; use Defuse\Crypto\Exception\CryptoException; use SP\Domain\Common\Services\ServiceException; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\NoSuchPropertyException; use SP\Domain\Core\Exceptions\QueryException; -use SP\Domain\Plugin\Ports\PluginDataServiceInterface; -use SP\Domain\Plugin\Services\PluginDataService; +use SP\Domain\Plugin\Ports\PluginDataInterface; +use SP\Domain\Plugin\Ports\PluginOperationInterface; use SP\Infrastructure\Common\Repositories\NoSuchItemException; use SP\Infrastructure\Plugin\Repositories\PluginDataModel; /** * Class PluginOperation - * - * @package SP\Plugin */ -final class PluginOperation +final class PluginOperation implements PluginOperationInterface { - private PluginDataService $pluginDataService; - private string $pluginName; - - /** - * PluginOperation constructor. - * - * @param PluginDataServiceInterface $pluginDataService - * @param string $pluginName - */ public function __construct( - PluginDataServiceInterface $pluginDataService, - string $pluginName - ) - { - $this->pluginDataService = $pluginDataService; - $this->pluginName = $pluginName; + private readonly PluginDataInterface $pluginDataService, + private readonly string $pluginName + ) { } /** - * @param int $itemId + * @param int $itemId * @param mixed $data * * @return int @@ -70,18 +56,15 @@ final class PluginOperation * @throws QueryException * @throws ServiceException */ - public function create(int $itemId, $data): int + public function create(int $itemId, mixed $data): int { - $itemData = new PluginDataModel(); - $itemData->setName($this->pluginName); - $itemData->setItemId($itemId); - $itemData->setData(serialize($data)); + $itemData = new PluginDataModel(['name' => $this->pluginName, 'itemId' => $itemId, 'data' => serialize($data)]); return $this->pluginDataService->create($itemData)->getLastId(); } /** - * @param int $itemId + * @param int $itemId * @param mixed $data * * @return int @@ -91,12 +74,9 @@ final class PluginOperation * @throws QueryException * @throws ServiceException */ - public function update(int $itemId, $data): int + public function update(int $itemId, mixed $data): int { - $itemData = new PluginDataModel(); - $itemData->setName($this->pluginName); - $itemData->setItemId($itemId); - $itemData->setData(serialize($data)); + $itemData = new PluginDataModel(['name' => $this->pluginName, 'itemId' => $itemId, 'data' => serialize($data)]); return $this->pluginDataService->update($itemData); } @@ -112,13 +92,19 @@ final class PluginOperation } /** + * @template T + * + * @param int $itemId + * @param class-string|null $class + * + * @return mixed|null * @throws ConstraintException * @throws CryptoException * @throws NoSuchPropertyException * @throws QueryException * @throws ServiceException */ - public function get(int $itemId, ?string $class = null) + public function get(int $itemId, ?string $class = null): mixed { try { return $this->pluginDataService diff --git a/lib/SP/Domain/Plugin/Services/PluginRegister.php b/lib/SP/Domain/Plugin/Services/PluginRegister.php new file mode 100644 index 00000000..6173c540 --- /dev/null +++ b/lib/SP/Domain/Plugin/Services/PluginRegister.php @@ -0,0 +1,106 @@ +. + */ + +namespace SP\Domain\Plugin\Services; + +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Common\Services\Service; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Domain\Plugin\Ports\PluginInterface; +use SP\Domain\Plugin\Ports\PluginManagerInterface; +use SP\Domain\Plugin\Ports\PluginRegisterInterface; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; +use SP\Infrastructure\Plugin\Repositories\PluginModel; + +use function SP\__u; + +/** + * Class PluginRegister + */ +final class PluginRegister extends Service implements PluginRegisterInterface +{ + public function __construct(Application $application, private readonly PluginManagerInterface $pluginService) + { + parent::__construct($application); + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + public function registerFor(PluginInterface $plugin): void + { + try { + $this->pluginService->getByName($plugin->getName()); + + $this->eventDispatcher->notify( + 'register.plugin', + new Event( + $this, + EventMessage::factory() + ->addDescription(__u('Plugin already registered')) + ->addDetail(__u('Name'), $plugin->getName()) + ) + ); + } catch (NoSuchItemException) { + $this->eventDispatcher->notify( + 'register.plugin', + new Event( + $this, + EventMessage::factory() + ->addDescription(__u('Plugin not registered yet')) + ->addDetail(__u('Name'), $plugin->getName()) + ) + ); + + $this->register($plugin); + } + } + + /** + * @throws ConstraintException + * @throws QueryException + */ + private function register(PluginInterface $plugin): void + { + $pluginData = new PluginModel(); + $pluginData->setName($plugin->getName()); + $pluginData->setEnabled(false); + + $this->pluginService->create($pluginData); + + $this->eventDispatcher->notify( + 'create.plugin', + new Event( + $this, + EventMessage::factory() + ->addDescription(__u('New Plugin')) + ->addDetail(__u('Name'), $plugin->getName()) + ) + ); + } +} diff --git a/lib/SP/Domain/Plugin/Services/PluginUpgrader.php b/lib/SP/Domain/Plugin/Services/PluginUpgrader.php new file mode 100644 index 00000000..29af4cc7 --- /dev/null +++ b/lib/SP/Domain/Plugin/Services/PluginUpgrader.php @@ -0,0 +1,114 @@ +. + */ + +namespace SP\Domain\Plugin\Services; + +use SP\Core\Application; +use SP\Core\Events\Event; +use SP\Core\Events\EventMessage; +use SP\Domain\Common\Services\Service; +use SP\Domain\Core\Exceptions\ConstraintException; +use SP\Domain\Core\Exceptions\QueryException; +use SP\Domain\Plugin\Ports\PluginDataInterface; +use SP\Domain\Plugin\Ports\PluginInterface; +use SP\Domain\Plugin\Ports\PluginManagerInterface; +use SP\Domain\Plugin\Ports\PluginUpgraderInterface; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; +use SP\Util\VersionUtil; + +use function SP\__; +use function SP\__u; + +/** + * Class PluginUpgrader + */ +final class PluginUpgrader extends Service implements PluginUpgraderInterface +{ + public function __construct( + Application $application, + private readonly PluginManagerInterface $pluginService, + private readonly PluginDataInterface $pluginDataService + ) { + parent::__construct($application); + } + + /** + * @param PluginInterface $plugin + * @param string $version + * @throws ConstraintException + * @throws QueryException + */ + public function upgradeFor(PluginInterface $plugin, string $version): void + { + try { + $pluginModel = $this->pluginService->getByName($plugin->getName()); + } catch (NoSuchItemException $e) { + $this->eventDispatcher->notify( + 'plugin.upgrade', + new Event( + $e, + EventMessage::factory() + ->addDetail(__('Plugin not registered'), $plugin->getName()) + ) + ); + + return; + } + + if ($pluginModel->getVersionLevel() === null + || VersionUtil::checkVersion($pluginModel->getVersionLevel(), $version) + ) { + $this->eventDispatcher->notify( + 'plugin.upgrade.process', + new Event( + $this, + EventMessage::factory() + ->addDescription(__u('Upgrading plugin')) + ->addDetail(__u('Name'), $plugin->getName()) + ) + ); + + $plugin->onUpgrade( + $version, + new PluginOperation($this->pluginDataService, $plugin->getName()), + $pluginModel + ); + + $pluginModel->setData(null); + $pluginModel->setVersionLevel($version); + + $this->pluginService->update($pluginModel); + + $this->eventDispatcher->notify( + 'plugin.upgrade.process', + new Event( + $this, + EventMessage::factory() + ->addDescription(__u('Plugin upgraded')) + ->addDetail(__u('Name'), $plugin->getName()) + ) + ); + } + } +} diff --git a/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php b/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php index c6f62e31..a35e41ca 100644 --- a/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php +++ b/lib/SP/Infrastructure/Plugin/Repositories/PluginDataModel.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -26,76 +26,33 @@ namespace SP\Infrastructure\Plugin\Repositories; use SP\DataModel\EncryptedModel; use SP\Domain\Common\Adapters\HydratableInterface; +use SP\Domain\Common\Models\Model; use SP\Domain\Common\Models\SerializedModel; /** - * Class PluginData - * - * @package SP\Infrastructure\Plugin\Repositories + * Class PluginDataModel */ -final class PluginDataModel implements HydratableInterface +final class PluginDataModel extends Model implements HydratableInterface { use SerializedModel; use EncryptedModel; - /** - * @var string - */ - private $name; - /** - * @var int - */ - private $itemId; - /** - * @var string - */ - private $data; + protected ?string $name = null; + protected ?int $itemId = null; + protected ?string $data = null; - /** - * @return string - */ - public function getName(): string + public function getName(): ?string { return $this->name; } - /** - * @param string $name - */ - public function setName(string $name) + public function getItemId(): ?int { - $this->name = $name; + return $this->itemId; } - /** - * @return int - */ - public function getItemId(): int - { - return (int)$this->itemId; - } - - /** - * @param int $itemId - */ - public function setItemId(int $itemId) - { - $this->itemId = $itemId; - } - - /** - * @return string - */ - public function getData(): string + public function getData(): ?string { return $this->data; } - - /** - * @param string $data - */ - public function setData(string $data) - { - $this->data = $data; - } } diff --git a/lib/SP/Infrastructure/Plugin/Repositories/PluginModel.php b/lib/SP/Infrastructure/Plugin/Repositories/PluginModel.php index 59c8e87a..86a2a8f8 100644 --- a/lib/SP/Infrastructure/Plugin/Repositories/PluginModel.php +++ b/lib/SP/Infrastructure/Plugin/Repositories/PluginModel.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -28,48 +28,24 @@ use SP\Domain\Common\Adapters\DataModelInterface; use SP\Domain\Common\Models\Model; /** - * Class PluginData - * - * @package SP\DataModel + * Class PluginModel */ class PluginModel extends Model implements DataModelInterface { - /** - * @var int - */ - protected $id; - /** - * @var string - */ - protected $name; - /** - * @var string - */ - protected $data; - /** - * @var int - */ - protected $enabled = 0; - /** - * @var int - */ - protected $available = 1; - /** - * @var string - */ - protected $versionLevel; + protected ?int $id = null; + protected ?string $name = null; + protected ?string $data = null; + protected ?bool $enabled = null; + protected ?string $versionLevel = null; public function getId(): ?int { - return (int)$this->id; + return $this->id; } - /** - * @param int $id - */ - public function setId($id) + public function setId(int $id): void { - $this->id = (int)$id; + $this->id = $id; } public function getName(): ?string @@ -77,74 +53,38 @@ class PluginModel extends Model implements DataModelInterface return $this->name; } - /** - * @param string $name - */ - public function setName($name) + public function setName(string $name): void { $this->name = $name; } - /** - * @return string - */ - public function getData() + public function getData(): ?string { return $this->data; } - /** - * @param string $data - */ - public function setData($data) + public function setData(string $data): void { $this->data = $data; } - /** - * @return int - */ - public function getEnabled() + public function getEnabled(): ?bool { - return (int)$this->enabled; + return $this->enabled; } - /** - * @param int $enabled - */ - public function setEnabled($enabled) + public function setEnabled(bool $enabled): void { - $this->enabled = (int)$enabled; + $this->enabled = $enabled; } - /** - * @return int - */ - public function getAvailable() - { - return (int)$this->available; - } - /** - * @param int $available - */ - public function setAvailable($available) - { - $this->available = (int)$available; - } - - /** - * @return string - */ - public function getVersionLevel() + public function getVersionLevel(): ?string { return $this->versionLevel; } - /** - * @param string $versionLevel - */ - public function setVersionLevel(string $versionLevel) + public function setVersionLevel(string $versionLevel): void { $this->versionLevel = $versionLevel; } diff --git a/lib/SP/Plugin/PluginBase.php b/lib/SP/Plugin/PluginBase.php index 260506e5..aec6fd59 100644 --- a/lib/SP/Plugin/PluginBase.php +++ b/lib/SP/Plugin/PluginBase.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2021, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -25,104 +25,62 @@ namespace SP\Plugin; use Defuse\Crypto\Exception\CryptoException; -use Psr\Container\ContainerInterface; use SP\Domain\Common\Services\ServiceException; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\NoSuchPropertyException; use SP\Domain\Core\Exceptions\QueryException; -use SP\Domain\Plugin\Services\PluginService; +use SP\Domain\Plugin\Ports\PluginCompatilityInterface; +use SP\Domain\Plugin\Ports\PluginInterface; +use SP\Domain\Plugin\Ports\PluginLoaderInterface; +use SP\Domain\Plugin\Ports\PluginOperationInterface; +use SP\Infrastructure\Common\Repositories\NoSuchItemException; /** * Class PluginBase - * - * @package SP\Plugin */ abstract class PluginBase implements PluginInterface { - /** - * @var string|null Directorio base - */ protected ?string $base = null; - /** - * @var string|null Tipo de plugin - */ - protected ?string $type = null; protected ?string $themeDir = null; - /** - * @var mixed - */ - protected $data; - protected ?int $enabled; - protected PluginOperation $pluginOperation; - private PluginService $pluginService; + protected mixed $data; /** - * PluginBase constructor. - * - * @param ContainerInterface $dic - * @param PluginOperation $pluginOperation + * @throws ConstraintException + * @throws NoSuchItemException + * @throws QueryException */ - final public function __construct( - ContainerInterface $dic, - PluginOperation $pluginOperation - ) - { - /** @noinspection UnusedConstructorDependenciesInspection */ - $this->pluginService = $dic->get(PluginService::class); - $this->pluginOperation = $pluginOperation; - $this->init($dic); + public function __construct( + protected readonly PluginOperationInterface $pluginOperation, + private readonly PluginCompatilityInterface $pluginCompatilityService, + private readonly PluginLoaderInterface $pluginLoadService + ) { + $this->load(); } /** - * @return string + * @throws ConstraintException + * @throws NoSuchItemException + * @throws QueryException */ - public function getType(): ?string + private function load(): void { - return $this->type; + if ($this->pluginCompatilityService->checkFor($this)) { + $this->pluginLoadService->loadFor($this); + } } - /** - * @param string $type - */ - public function setType(string $type): void - { - $this->type = $type; - } - - /** - * @return string - */ public function getThemeDir(): ?string { return $this->themeDir; } - /** - * @return mixed - */ - public function getData() + public function getData(): mixed { return $this->data; } /** - * @return int - */ - public function getEnabled(): ?int - { - return $this->enabled; - } - - /** - * @param int $enabled - */ - public function setEnabled(int $enabled): void - { - $this->enabled = $enabled; - } - - /** - * @param int $id + * @param int $id * @param mixed $data * * @throws CryptoException @@ -131,7 +89,7 @@ abstract class PluginBase implements PluginInterface * @throws QueryException * @throws ServiceException */ - final public function saveData(int $id, $data): void + final public function saveData(int $id, mixed $data): void { if ($this->data === null) { $this->pluginOperation->create($id, $data); @@ -142,21 +100,15 @@ abstract class PluginBase implements PluginInterface $this->data = $data; } - /** - * Establecer las locales del plugin - */ protected function setLocales(): void { - $locales = $this->getBase() . DIRECTORY_SEPARATOR . 'locales'; + $locales = sprintf('%s%slocales', $this->getBase(), DIRECTORY_SEPARATOR); $name = strtolower($this->getName()); bindtextdomain($name, $locales); bind_textdomain_codeset($name, 'UTF-8'); } - /** - * @return string - */ public function getBase(): ?string { return $this->base; diff --git a/lib/SP/Plugin/PluginEventReceiver.php b/lib/SP/Plugin/PluginEventReceiver.php deleted file mode 100644 index b6993ad7..00000000 --- a/lib/SP/Plugin/PluginEventReceiver.php +++ /dev/null @@ -1,73 +0,0 @@ -. - */ - -namespace SP\Plugin; - -use Psr\Container\ContainerInterface; -use SP\Core\Events\Event; -use SplObserver; - -/** - * Interface EventReceiver - * - * @package SP\Core\Events - */ -interface PluginEventReceiver extends SplObserver -{ - /** - * Inicialización del observador - * - * @param ContainerInterface $dic - */ - public function init(ContainerInterface $dic); - - /** - * Evento de actualización - * - * @param string $eventType Nombre del evento - * @param Event $event Objeto del evento - */ - public function updateEvent($eventType, Event $event); - - /** - * Devuelve los eventos que implementa el observador - * - * @return array - */ - public function getEvents(); - - /** - * Devuelve los recursos Javascript necesarios para el plugin - * - * @return array - */ - public function getJsResources(); - - /** - * Devuelve los recursos CSS necesarios para el plugin - * - * @return array - */ - public function getCssResources(); -} \ No newline at end of file diff --git a/lib/SP/Plugin/PluginManager.php b/lib/SP/Plugin/PluginManager.php deleted file mode 100644 index 1828c681..00000000 --- a/lib/SP/Plugin/PluginManager.php +++ /dev/null @@ -1,521 +0,0 @@ -. - */ - -namespace SP\Plugin; - -use Exception; -use ReflectionClass; -use SP\Core\Bootstrap\BootstrapBase; -use SP\Core\Events\Event; -use SP\Core\Events\EventDispatcher; -use SP\Core\Events\EventMessage; -use SP\Domain\Core\Exceptions\ConstraintException; -use SP\Domain\Core\Exceptions\QueryException; -use SP\Domain\Core\Exceptions\SPException; -use SP\Domain\Install\Services\InstallerService; -use SP\Domain\Plugin\Ports\PluginDataServiceInterface; -use SP\Domain\Plugin\Ports\PluginServiceInterface; -use SP\Domain\Plugin\Services\PluginDataService; -use SP\Domain\Plugin\Services\PluginService; -use SP\Infrastructure\Common\Repositories\NoSuchItemException; -use SP\Infrastructure\Plugin\Repositories\PluginModel; -use SP\Util\VersionUtil; - -/** - * Class PluginUtil - * - * @package SP\Plugin - */ -class PluginManager -{ - private static ?array $pluginsAvailable; - private ?array $enabledPlugins = null; - /** - * @var PluginInterface[] Plugins ya cargados - */ - private array $loadedPlugins = []; - private array $disabledPlugins = []; - private PluginService $pluginService; - private EventDispatcher $eventDispatcher; - private PluginDataService $pluginDataService; - - /** - * PluginManager constructor. - * - * @param PluginServiceInterface $pluginService - * @param PluginDataServiceInterface $pluginDataService - * @param EventDispatcher $eventDispatcher - */ - public function __construct( - PluginServiceInterface $pluginService, - PluginDataServiceInterface $pluginDataService, - EventDispatcher $eventDispatcher - ) { - $this->pluginService = $pluginService; - $this->pluginDataService = $pluginDataService; - $this->eventDispatcher = $eventDispatcher; - - self::$pluginsAvailable = self::getPlugins(); - } - - /** - * Devuelve la lista de Plugins disponibles em el directorio - * - * @return array - */ - public static function getPlugins(): array - { - $plugins = []; - - if (is_dir(PLUGINS_PATH) - && ($dir = dir(PLUGINS_PATH))) { - - while (false !== ($entry = $dir->read())) { - $pluginDir = PLUGINS_PATH.DS.$entry; - $pluginFile = $pluginDir.DS.'src'.DS.'lib'.DS.'Plugin.php'; - - if (strpos($entry, '.') === false - && is_dir($pluginDir) - && file_exists($pluginFile) - ) { - logger(sprintf('Plugin found: %s', $pluginDir)); - - $plugins[$entry] = require $pluginDir.DS.'base.php'; - } - } - - $dir->close(); - } - - return $plugins; - } - - /** - * Obtener la información de un plugin - * - * @param string $name Nombre del plugin - * @param bool $initialize - * - * @return PluginInterface - */ - public function getPlugin(string $name, bool $initialize = false): ?PluginInterface - { - if (isset(self::$pluginsAvailable[$name])) { - $plugin = $this->loadPluginClass( - $name, - self::$pluginsAvailable[$name]['namespace'] - ); - - if (null !== $plugin && $initialize) { - $this->initPlugin($plugin); - $plugin->onLoad(); - } - - return $plugin; - } - - return null; - } - - /** - * Cargar un plugin - * - * @param string $name Nombre del plugin - * @param string $namespace - * - * @return PluginInterface - */ - private function loadPluginClass(string $name, string $namespace): ?PluginInterface - { - $pluginName = ucfirst($name); - - if (isset($this->loadedPlugins[$pluginName])) { - return $this->loadedPlugins[$pluginName]; - } - - try { - $class = $namespace.'Plugin'; - $reflectionClass = new ReflectionClass($class); - - /** @var PluginInterface $plugin */ - $plugin = $reflectionClass->newInstance( - BootstrapBase::getContainer(), // FIXME - new PluginOperation($this->pluginDataService, $pluginName) - ); - - // Do not load plugin's data if not compatible. - // Just return the plugin instance before disabling it - if ($this->checkCompatibility($plugin) === false) { - $this->eventDispatcher->notify( - 'plugin.load.error', - new Event( - $this, EventMessage::factory() - ->addDescription( - sprintf(__('Plugin version not compatible (%s)'), implode('.', $plugin->getVersion())) - ) - ) - ); - - $this->disabledPlugins[] = $pluginName; - } - - return $plugin; - } catch (Exception $e) { - processException($e); - - $this->eventDispatcher->notify( - 'exception', - new Event( - $e, EventMessage::factory() - ->addDescription(sprintf(__('Unable to load the "%s" plugin'), $pluginName)) - ->addDescription($e->getMessage()) - ->addDetail(__u('Plugin'), $pluginName) - ) - ); - } - - return null; - } - - /** - * @param PluginInterface $plugin - * - * @return bool - * @throws ConstraintException - * @throws NoSuchItemException - * @throws QueryException - */ - public function checkCompatibility(PluginInterface $plugin): bool - { - $pluginVersion = implode('.', $plugin->getCompatibleVersion()); - $appVersion = implode('.', array_slice(InstallerService::VERSION, 0, 2)); - - if (version_compare($pluginVersion, $appVersion) === -1) { - $this->pluginService->toggleEnabledByName( - $plugin->getName(), - false - ); - - $this->eventDispatcher->notify( - 'edit.plugin.disable', - new Event( - $this, - EventMessage::factory() - ->addDetail(__u('Plugin disabled'), $plugin->getName()) - ) - ); - - return false; - } - - return true; - } - - /** - * @param PluginInterface $plugin - * - * @return bool - */ - private function initPlugin(PluginInterface $plugin): bool - { - try { - $pluginModel = $this->pluginService->getByName($plugin->getName()); - - if ($pluginModel->getEnabled() !== 1) { - $this->disabledPlugins[] = $plugin->getName(); - } - - return true; - } catch (Exception $e) { - processException($e); - - $this->eventDispatcher->notify( - 'exception', - new Event( - $e, EventMessage::factory() - ->addDescription(sprintf(__('Unable to load the "%s" plugin'), $plugin->getName())) - ->addDescription($e->getMessage()) - ->addDetail(__u('Plugin'), $plugin->getName()) - ) - ); - } - - return false; - } - - /** - * Loads the available and enabled plugins - * - * @throws ConstraintException - * @throws QueryException - * @throws SPException - */ - public function loadPlugins(): void - { - $available = array_keys(self::$pluginsAvailable); - $processed = []; - - // Process registered plugins in the database - foreach ($this->pluginService->getAll() as $plugin) { - $in = in_array($plugin->getName(), $available, true); - - if ($in === true) { - if ($plugin->getEnabled() === 1) { - $this->load($plugin->getName()); - } - - if ($plugin->getAvailable() === 0) { - $this->pluginService->toggleAvailable($plugin->getId(), true); - - $this->eventDispatcher->notify( - 'edit.plugin.available', - new Event( - $this, EventMessage::factory() - ->addDetail(__u('Plugin available'), $plugin->getName()) - ) - ); - - $this->load($plugin->getName()); - } - } else { - if ($plugin->getAvailable() === 1) { - $this->pluginService->toggleAvailable($plugin->getId(), false); - - $this->eventDispatcher->notify( - 'edit.plugin.unavailable', - new Event( - $this, EventMessage::factory() - ->addDetail(__u('Plugin unavailable'), $plugin->getName()) - ) - ); - } - } - - $processed[] = $plugin->getName(); - } - - // Search for available plugins and not registered in the database - foreach (array_diff($available, $processed) as $plugin) { - $this->registerPlugin($plugin); - - $this->load($plugin); - } - } - - /** - * @param string $pluginName - */ - private function load(string $pluginName): void - { - $plugin = $this->loadPluginClass( - $pluginName, - self::$pluginsAvailable[$pluginName]['namespace'] - ); - - if ($plugin !== null - && $this->initPlugin($plugin) - ) { - logger(sprintf('Plugin loaded: %s', $pluginName)); - - $this->eventDispatcher->notify( - 'plugin.load', - new Event( - $this, EventMessage::factory() - ->addDetail(__u('Plugin loaded'), $pluginName) - ) - ); - - $this->loadedPlugins[$pluginName] = $plugin; - - $this->eventDispatcher->attach($plugin); - } - } - - /** - * @param string $name - * - * @throws ConstraintException - * @throws QueryException - */ - private function registerPlugin(string $name): void - { - $pluginData = new PluginModel(); - $pluginData->setName($name); - $pluginData->setEnabled(false); - - $this->pluginService->create($pluginData); - - $this->eventDispatcher->notify( - 'create.plugin', - new Event( - $this, EventMessage::factory() - ->addDescription(__u('New Plugin')) - ->addDetail(__u('Name'), $name) - ) - ); - - $this->disabledPlugins[] = $name; - } - - /** - * @param string $version - */ - public function upgradePlugins(string $version): void - { - $available = array_keys(self::$pluginsAvailable); - - foreach ($available as $pluginName) { - $plugin = $this->loadPluginClass( - $pluginName, - self::$pluginsAvailable[$pluginName]['namespace'] - ); - - if (null === $plugin) { - $this->eventDispatcher->notify( - 'upgrade.plugin.process', - new Event( - $this, EventMessage::factory() - ->addDescription(sprintf(__('Unable to upgrade the "%s" plugin'), $pluginName)) - ->addDetail(__u('Plugin'), $pluginName) - ) - ); - - continue; - } - - try { - $pluginModel = $this->pluginService->getByName($pluginName); - - if ($pluginModel->getVersionLevel() === null - || VersionUtil::checkVersion($pluginModel->getVersionLevel(), $version) - ) { - $this->eventDispatcher->notify( - 'upgrade.plugin.process', - new Event( - $this, EventMessage::factory() - ->addDescription(__u('Upgrading plugin')) - ->addDetail(__u('Name'), $pluginName) - ) - ); - - $plugin->upgrade( - $version, - new PluginOperation($this->pluginDataService, $pluginName), - $pluginModel - ); - - $pluginModel->setData(null); - $pluginModel->setVersionLevel($version); - - $this->pluginService->update($pluginModel); - - $this->eventDispatcher->notify( - 'upgrade.plugin.process', - new Event( - $this, EventMessage::factory() - ->addDescription(__u('Plugin upgraded')) - ->addDetail(__u('Name'), $pluginName) - ) - ); - } - } catch (Exception $e) { - processException($e); - - $this->eventDispatcher->notify( - 'exception', - new Event( - $e, EventMessage::factory() - ->addDescription(sprintf(__('Unable to upgrade the "%s" plugin'), $pluginName)) - ->addDescription($e->getMessage()) - ->addDetail(__u('Plugin'), $pluginName) - ) - ); - } - } - } - - /** - * Comprobar disponibilidad de plugins habilitados - * - * @throws SPException - */ - public function checkEnabledPlugins(): void - { - foreach ($this->getEnabledPlugins() as $plugin) { - if (!in_array($plugin, $this->loadedPlugins, true)) { - $this->pluginService->toggleAvailableByName($plugin, false); - - $this->eventDispatcher->notify( - 'edit.plugin.unavailable', - new Event( - $this, EventMessage::factory() - ->addDetail(__u('Plugin disabled'), $plugin->getName()) - ) - ); - } - } - } - - /** - * Devolver los plugins habilitados - * - * @return array - * @throws ConstraintException - * @throws QueryException - */ - public function getEnabledPlugins(): ?array - { - if ($this->enabledPlugins !== null) { - return $this->enabledPlugins; - } - - $this->enabledPlugins = []; - - foreach ($this->pluginService->getEnabled() as $plugin) { - $this->enabledPlugins[] = $plugin->getName(); - } - - return $this->enabledPlugins; - } - - /** - * Devolver los plugins cargados - * - * @return PluginInterface[] - */ - public function getLoadedPlugins(): array - { - return $this->loadedPlugins; - } - - /** - * Devolver los plugins deshabilidatos - * - * @return string[] - */ - public function getDisabledPlugins(): array - { - return $this->disabledPlugins; - } -} diff --git a/tests/SP/Services/Plugin/PluginDataServiceTest.php b/tests/SP/Services/Plugin/PluginDataServiceTest.php index b2e7207e..2eb2a95a 100644 --- a/tests/SP/Services/Plugin/PluginDataServiceTest.php +++ b/tests/SP/Services/Plugin/PluginDataServiceTest.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -33,8 +33,8 @@ use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\NoSuchPropertyException; use SP\Domain\Core\Exceptions\QueryException; use SP\Domain\Core\Exceptions\SPException; -use SP\Domain\Plugin\Ports\PluginDataServiceInterface; -use SP\Domain\Plugin\Services\PluginDataService; +use SP\Domain\Plugin\Ports\PluginDataInterface; +use SP\Domain\Plugin\Services\PluginData; use SP\Infrastructure\Common\Repositories\NoSuchItemException; use SP\Infrastructure\Plugin\Repositories\PluginDataModel; use SP\Tests\DatabaseTestCase; @@ -49,7 +49,7 @@ use function SP\Tests\setupContext; class PluginDataServiceTest extends DatabaseTestCase { /** - * @var PluginDataServiceInterface + * @var PluginDataInterface */ private static $service; @@ -65,7 +65,7 @@ class PluginDataServiceTest extends DatabaseTestCase self::$loadFixtures = true; // Inicializar el servicio - self::$service = $dic->get(PluginDataService::class); + self::$service = $dic->get(PluginData::class); } /** diff --git a/tests/SP/Services/Plugin/PluginOperationTest.php b/tests/SP/Services/Plugin/PluginOperationTest.php index e6ccc2ed..99af4b89 100644 --- a/tests/SP/Services/Plugin/PluginOperationTest.php +++ b/tests/SP/Services/Plugin/PluginOperationTest.php @@ -1,10 +1,10 @@ . + * along with sysPass. If not, see . */ namespace SP\Tests\Services\Plugin; @@ -33,9 +33,10 @@ use SP\Domain\Common\Services\ServiceException; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\NoSuchPropertyException; use SP\Domain\Core\Exceptions\QueryException; -use SP\Domain\Plugin\Services\PluginDataService; +use SP\Domain\Plugin\Ports\PluginOperationInterface; +use SP\Domain\Plugin\Services\PluginData; +use SP\Domain\Plugin\Services\PluginOperation; use SP\Infrastructure\Common\Repositories\NoSuchItemException; -use SP\Plugin\PluginOperation; use SP\Tests\DatabaseTestCase; use stdClass; @@ -67,7 +68,7 @@ class PluginOperationTest extends DatabaseTestCase // Inicializar el servicio self::$pluginOperation = function ($name) use ($dic) { - return new PluginOperation($dic->get(PluginDataService::class), $name); + return new PluginOperation($dic->get(PluginData::class), $name); }; } @@ -80,7 +81,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testUpdate() { - /** @var PluginOperation $pluginOperation */ + /** @var \SP\Domain\Plugin\Ports\PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Authenticator'); $data = [1, 2, 3]; @@ -106,7 +107,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testUpdateUnknown() { - /** @var PluginOperation $pluginOperation */ + /** @var \SP\Domain\Plugin\Ports\PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Authenticator'); $data = [1, 2, 3]; @@ -125,7 +126,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testUpdateWrongPlugin() { - /** @var PluginOperation $pluginOperation */ + /** @var PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Test'); $data = [1, 2, 3]; @@ -142,7 +143,7 @@ class PluginOperationTest extends DatabaseTestCase { $this->assertEquals(4, self::getRowCount('PluginData')); - /** @var PluginOperation $pluginOperation */ + /** @var PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Authenticator'); $pluginOperation->delete(1); @@ -156,7 +157,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testDeleteUnknown() { - /** @var PluginOperation $pluginOperation */ + /** @var PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Authenticator'); $this->expectException(NoSuchItemException::class); @@ -179,7 +180,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testGetUnknown() { - /** @var PluginOperation $pluginOperation */ + /** @var PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Authenticator'); $this->assertNull($pluginOperation->get(4)); @@ -194,7 +195,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testCreate() { - /** @var PluginOperation $pluginOperation */ + /** @var PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Authenticator'); $data = new stdClass(); @@ -216,7 +217,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testCreateDuplicated() { - /** @var PluginOperation $pluginOperation */ + /** @var \SP\Domain\Plugin\Ports\PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Authenticator'); $data = new stdClass(); @@ -238,7 +239,7 @@ class PluginOperationTest extends DatabaseTestCase */ public function testCreateWrongPlugin() { - /** @var PluginOperation $pluginOperation */ + /** @var PluginOperationInterface $pluginOperation */ $pluginOperation = self::$pluginOperation->call($this, 'Test'); $data = new stdClass(); diff --git a/tests/SP/Services/Plugin/PluginServiceTest.php b/tests/SP/Services/Plugin/PluginServiceTest.php index 7db51968..6bfc963f 100644 --- a/tests/SP/Services/Plugin/PluginServiceTest.php +++ b/tests/SP/Services/Plugin/PluginServiceTest.php @@ -4,7 +4,7 @@ * * @author nuxsmin * @link https://syspass.org - * @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org + * @copyright 2012-2023, Rubén Domínguez nuxsmin@$syspass.org * * This file is part of sysPass. * @@ -33,8 +33,8 @@ use SP\Domain\Common\Services\ServiceException; use SP\Domain\Core\Exceptions\ConstraintException; use SP\Domain\Core\Exceptions\QueryException; use SP\Domain\Core\Exceptions\SPException; -use SP\Domain\Plugin\Ports\PluginServiceInterface; -use SP\Domain\Plugin\Services\PluginService; +use SP\Domain\Plugin\Ports\PluginManagerInterface; +use SP\Domain\Plugin\Services\PluginManager; use SP\Infrastructure\Common\Repositories\NoSuchItemException; use SP\Infrastructure\Plugin\Repositories\PluginModel; use SP\Tests\DatabaseTestCase; @@ -49,7 +49,7 @@ use function SP\Tests\setupContext; class PluginServiceTest extends DatabaseTestCase { /** - * @var PluginServiceInterface + * @var PluginManagerInterface */ private static $service; @@ -65,7 +65,7 @@ class PluginServiceTest extends DatabaseTestCase self::$loadFixtures = true; // Inicializar el servicio - self::$service = $dic->get(PluginService::class); + self::$service = $dic->get(PluginManager::class); } /**