2024-04-28 17:14:45 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Modules\Plugins;
|
|
|
|
|
2024-05-01 14:48:05 +00:00
|
|
|
use App\Entities\Episode;
|
|
|
|
use App\Entities\Podcast;
|
|
|
|
use App\Libraries\SimpleRSSElement;
|
2024-05-05 09:14:30 +00:00
|
|
|
use Config\Database;
|
2024-05-01 14:48:05 +00:00
|
|
|
|
|
|
|
/**
|
2024-05-01 15:41:13 +00:00
|
|
|
* @method void channelTag(Podcast $podcast, SimpleRSSElement $channel)
|
|
|
|
* @method void itemTag(Episode $episode, SimpleRSSElement $item)
|
|
|
|
* @method string siteHead()
|
2024-05-01 14:48:05 +00:00
|
|
|
*/
|
2024-04-28 17:14:45 +00:00
|
|
|
class Plugins
|
|
|
|
{
|
2024-05-01 18:37:38 +00:00
|
|
|
public const API_VERSION = '1.0';
|
2024-05-01 14:48:05 +00:00
|
|
|
|
2024-04-28 17:14:45 +00:00
|
|
|
/**
|
2024-05-01 14:48:05 +00:00
|
|
|
* @var list<string>
|
|
|
|
*/
|
2024-05-01 18:37:38 +00:00
|
|
|
public const HOOKS = ['channelTag', 'itemTag', 'siteHead'];
|
2024-05-01 14:48:05 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<BasePlugin>
|
2024-04-28 17:14:45 +00:00
|
|
|
*/
|
2024-04-29 16:03:00 +00:00
|
|
|
protected static array $plugins = [];
|
2024-04-28 17:14:45 +00:00
|
|
|
|
2024-05-01 14:48:05 +00:00
|
|
|
protected static int $installedCount = 0;
|
|
|
|
|
2024-04-29 16:03:00 +00:00
|
|
|
public function __construct()
|
2024-04-28 17:14:45 +00:00
|
|
|
{
|
2024-05-01 14:48:05 +00:00
|
|
|
helper('plugins');
|
|
|
|
|
2024-04-29 16:03:00 +00:00
|
|
|
$this->registerPlugins();
|
2024-04-28 17:14:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-05-01 14:48:05 +00:00
|
|
|
* @param value-of<static::HOOKS> $name
|
|
|
|
* @param array<mixed> $arguments
|
|
|
|
*/
|
|
|
|
public function __call(string $name, array $arguments): void
|
|
|
|
{
|
|
|
|
if (! in_array($name, static::HOOKS, true)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->runHook($name, $arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<BasePlugin>
|
2024-04-28 17:14:45 +00:00
|
|
|
*/
|
2024-05-01 14:48:05 +00:00
|
|
|
public function getPlugins(int $page, int $perPage): array
|
2024-04-28 17:14:45 +00:00
|
|
|
{
|
2024-05-01 14:48:05 +00:00
|
|
|
return array_slice(static::$plugins, (($page - 1) * $perPage), $perPage);
|
2024-04-29 16:03:00 +00:00
|
|
|
}
|
|
|
|
|
2024-05-02 15:32:27 +00:00
|
|
|
/**
|
|
|
|
* @return array<BasePlugin>
|
|
|
|
*/
|
|
|
|
public function getPluginsWithPodcastSettings(): array
|
|
|
|
{
|
|
|
|
$pluginsWithPodcastSettings = [];
|
|
|
|
foreach (static::$plugins as $plugin) {
|
|
|
|
if (! $plugin->isActive()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($plugin->settings['podcast'] === []) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$pluginsWithPodcastSettings[] = $plugin;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $pluginsWithPodcastSettings;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<BasePlugin>
|
|
|
|
*/
|
|
|
|
public function getPluginsWithEpisodeSettings(): array
|
|
|
|
{
|
|
|
|
$pluginsWithEpisodeSettings = [];
|
|
|
|
foreach (static::$plugins as $plugin) {
|
|
|
|
if (! $plugin->isActive()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($plugin->settings['episode'] === []) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$pluginsWithEpisodeSettings[] = $plugin;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $pluginsWithEpisodeSettings;
|
|
|
|
}
|
|
|
|
|
2024-05-01 18:37:38 +00:00
|
|
|
public function getPlugin(string $key): ?BasePlugin
|
|
|
|
{
|
|
|
|
foreach (static::$plugins as $plugin) {
|
|
|
|
if ($plugin->getKey() === $key) {
|
|
|
|
return $plugin;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-04-29 16:03:00 +00:00
|
|
|
/**
|
2024-05-01 14:48:05 +00:00
|
|
|
* @param value-of<static::HOOKS> $name
|
|
|
|
* @param array<mixed> $arguments
|
2024-04-29 16:03:00 +00:00
|
|
|
*/
|
2024-05-01 14:48:05 +00:00
|
|
|
public function runHook(string $name, array $arguments): void
|
2024-04-29 16:03:00 +00:00
|
|
|
{
|
|
|
|
foreach (static::$plugins as $plugin) {
|
2024-05-01 14:48:05 +00:00
|
|
|
// only run hook on active plugins
|
2024-05-01 18:37:38 +00:00
|
|
|
if (! $plugin->isActive()) {
|
|
|
|
continue;
|
2024-05-01 14:48:05 +00:00
|
|
|
}
|
2024-05-01 18:37:38 +00:00
|
|
|
|
|
|
|
if (! $plugin->isHookDeclared($name)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$plugin->{$name}(...$arguments);
|
2024-04-29 16:03:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-01 14:48:05 +00:00
|
|
|
public function activate(string $pluginKey): void
|
|
|
|
{
|
|
|
|
set_plugin_option($pluginKey, 'active', true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function deactivate(string $pluginKey): void
|
|
|
|
{
|
|
|
|
set_plugin_option($pluginKey, 'active', false);
|
|
|
|
}
|
|
|
|
|
2024-05-02 15:32:27 +00:00
|
|
|
/**
|
|
|
|
* @param ?array{'podcast'|'episode',int} $additionalContext
|
|
|
|
*/
|
|
|
|
public function setOption(string $pluginKey, string $name, mixed $value, array $additionalContext = null): void
|
2024-05-01 18:37:38 +00:00
|
|
|
{
|
2024-05-02 15:32:27 +00:00
|
|
|
set_plugin_option($pluginKey, $name, $value, $additionalContext);
|
2024-05-01 18:37:38 +00:00
|
|
|
}
|
|
|
|
|
2024-05-01 14:48:05 +00:00
|
|
|
public function getInstalledCount(): int
|
|
|
|
{
|
|
|
|
return static::$installedCount;
|
|
|
|
}
|
|
|
|
|
2024-05-05 09:14:30 +00:00
|
|
|
public function uninstall(string $pluginKey): bool
|
|
|
|
{
|
|
|
|
// remove all settings data
|
|
|
|
$db = Database::connect();
|
|
|
|
$builder = $db->table('settings');
|
|
|
|
|
|
|
|
$db->transStart();
|
|
|
|
$builder->where('class', self::class);
|
|
|
|
$builder->like('context', sprintf('plugin:%s', $pluginKey . '%'));
|
|
|
|
|
|
|
|
if (! $builder->delete()) {
|
|
|
|
$db->transRollback();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete plugin folder from PLUGINS_PATH
|
|
|
|
$pluginFolder = PLUGINS_PATH . $pluginKey;
|
|
|
|
$rmdirResult = $this->rrmdir($pluginFolder);
|
|
|
|
|
|
|
|
$transResult = $db->transCommit();
|
|
|
|
|
|
|
|
return $rmdirResult && $transResult;
|
|
|
|
}
|
|
|
|
|
2024-04-29 16:03:00 +00:00
|
|
|
protected function registerPlugins(): void
|
|
|
|
{
|
2024-05-01 14:48:05 +00:00
|
|
|
// search for plugins in plugins folder
|
2024-05-01 18:37:38 +00:00
|
|
|
// TODO: only get directories? Should be organized as author/repo?
|
2024-05-05 09:14:30 +00:00
|
|
|
$pluginsFiles = glob(PLUGINS_PATH . '**/Plugin.php');
|
2024-04-29 16:03:00 +00:00
|
|
|
|
2024-05-01 14:48:05 +00:00
|
|
|
if (! $pluginsFiles) {
|
|
|
|
return;
|
|
|
|
}
|
2024-04-29 16:03:00 +00:00
|
|
|
|
2024-05-01 14:48:05 +00:00
|
|
|
$locator = service('locator');
|
2024-04-29 16:03:00 +00:00
|
|
|
foreach ($pluginsFiles as $file) {
|
|
|
|
$className = $locator->findQualifiedNameFromPath($file);
|
|
|
|
|
|
|
|
if ($className === false) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-05-01 14:48:05 +00:00
|
|
|
$plugin = new $className(basename(dirname($file)), $file);
|
|
|
|
if (! $plugin instanceof BasePlugin) {
|
2024-04-29 16:03:00 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
static::$plugins[] = $plugin;
|
2024-05-01 14:48:05 +00:00
|
|
|
++static::$installedCount;
|
2024-04-29 16:03:00 +00:00
|
|
|
}
|
2024-04-28 17:14:45 +00:00
|
|
|
}
|
2024-05-05 09:14:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Adapted from https://stackoverflow.com/a/3338133
|
|
|
|
*/
|
|
|
|
private function rrmdir(string $dir): bool
|
|
|
|
{
|
|
|
|
if (! is_dir($dir)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$objects = scandir($dir);
|
|
|
|
|
|
|
|
if (! $objects) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($objects as $object) {
|
|
|
|
if ($object === '.') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($object === '..') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && ! is_link($dir . '/' . $object)) {
|
|
|
|
$this->rrmdir($dir . DIRECTORY_SEPARATOR . $object);
|
|
|
|
} else {
|
|
|
|
unlink($dir . DIRECTORY_SEPARATOR . $object);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rmdir($dir);
|
|
|
|
}
|
2024-04-28 17:14:45 +00:00
|
|
|
}
|