Commit 3d8aedf9 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat(plugins): add options to manifest for building forms and storing plugin settings

parent e80a33bf
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -114,7 +114,7 @@ class Autoload extends AutoloadConfig
    public function __construct()
    {
        // load plugins namespaces
        $pluginsPaths = glob(ROOTPATH . '/plugins/*', GLOB_ONLYDIR);
        $pluginsPaths = glob(ROOTPATH . '/plugins/*', GLOB_ONLYDIR | GLOB_NOSORT);

        if (! $pluginsPaths) {
            $pluginsPaths = [];
+57 −8
Original line number Diff line number Diff line
@@ -20,7 +20,9 @@ use RuntimeException;
 * @property string $license
 * @property string $compatible
 * @property string[] $keywords
 * @property string[] $hooks
 * @property string $iconSrc
 * @property array{settings:array{key:string,name:string,description:string}[],podcast:array{key:string,name:string,description:string}[],episode:array{key:string,name:string,description:string}[]} $options
 */
abstract class BasePlugin implements PluginInterface
{
@@ -76,6 +78,11 @@ abstract class BasePlugin implements PluginInterface
        return $this->active;
    }

    final public function isHookDeclared(string $name): bool
    {
        return in_array($name, $this->hooks, true);
    }

    final public function getKey(): string
    {
        return $this->key;
@@ -138,6 +145,31 @@ abstract class BasePlugin implements PluginInterface
            throw new RuntimeException('manifest.json is not a valid JSON', 1);
        }

        $validation = service('validation');

        if (array_key_exists('options', $manifest)) {
            $optionRules = [
                'key'         => 'required|alpha_numeric',
                'name'        => 'required|max_length[32]',
                'description' => 'permit_empty|max_length[128]',
            ];
            $defaultOption = [
                'key'         => '',
                'name'        => '',
                'description' => '',
            ];
            $validation->setRules($optionRules);
            foreach ($manifest['options'] as $key => $options) {
                foreach ($options as $key2 => $option) {
                    $manifest['options'][$key][$key2] = array_merge($defaultOption, $option);

                    if (! $validation->run($manifest['options'][$key][$key2])) {
                        dd($this->key, $manifest['options'][$key][$key2], $validation->getErrors());
                    }
                }
            }
        }

        $rules = [
            'name'         => 'required|max_length[32]',
            'version'      => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]',
@@ -145,22 +177,39 @@ abstract class BasePlugin implements PluginInterface
            'description'  => 'max_length[128]',
            'releaseDate'  => 'valid_date[Y-m-d]',
            'license'      => 'in_list[MIT]',
            'author.name'  => 'max_length[32]',
            'author.email' => 'valid_email',
            'author.url'   => 'valid_url_strict',
            'author.name'  => 'permit_empty|max_length[32]',
            'author.email' => 'permit_empty|valid_email',
            'author.url'   => 'permit_empty|valid_url_strict',
            'website'      => 'valid_url_strict',
            'keywords.*'   => 'in_list[seo,podcasting20,analytics]',
            'keywords.*'   => 'permit_empty|in_list[seo,podcasting20,analytics]',
            'hooks.*'      => 'permit_empty|in_list[' . implode(',', Plugins::HOOKS) . ']',
            'options'      => 'permit_empty',
        ];

        $validation = service('validation');

        $validation->setRules($rules);

        if (! $validation->run($manifest)) {
            dd($validation->getErrors());
        }
            dd($this->key, $manifest, $validation->getErrors());
        }

        $defaultAttributes = [
            'description' => '',
            'releaseDate' => '',
            'license'     => '',
            'author'      => [],
            'website'     => '',
            'hooks'       => [],
            'keywords'    => [],
            'options'     => [
                'settings' => [],
                'podcast'  => [],
                'episode'  => [],
            ],
        ];

        $validated = $validation->getValidated();

        return $validation->getValidated();
        return array_merge_recursive_distinct($defaultAttributes, $validated);
    }

    private function loadIcon(string $path): string
+8 −0
Original line number Diff line number Diff line
@@ -17,6 +17,14 @@ $routes->group(
                'as'     => 'plugins-installed',
                'filter' => 'permission:plugins.manage',
            ]);
            $routes->get('(:segment)', 'PluginsController::settings/$1', [
                'as'     => 'plugins-settings',
                'filter' => 'permission:plugins.manage',
            ]);
            $routes->post('(:segment)', 'PluginsController::settingsAction/$1', [
                'as'     => 'plugins-settings-action',
                'filter' => 'permission:plugins.manage',
            ]);
            $routes->post('activate/(:segment)', 'PluginsController::activate/$1', [
                'as'     => 'plugins-activate',
                'filter' => 'permission:plugins.manage',
+38 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ declare(strict_types=1);

namespace Modules\Plugins\Controllers;

use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Modules\Admin\Controllers\BaseController;
use Modules\Plugins\Plugins;
@@ -30,6 +31,43 @@ class PluginsController extends BaseController
        ]);
    }

    public function settings(string $pluginKey): string
    {
        /** @var Plugins $plugins */
        $plugins = service('plugins');

        $plugin = $plugins->getPlugin($pluginKey);

        if ($plugin === null) {
            throw PageNotFoundException::forPageNotFound();
        }

        helper('form');
        return view('plugins/settings', [
            'plugin' => $plugin,
        ]);
    }

    public function settingsAction(string $pluginKey): RedirectResponse
    {
        /** @var Plugins $plugins */
        $plugins = service('plugins');

        $plugin = $plugins->getPlugin($pluginKey);

        if ($plugin === null) {
            throw PageNotFoundException::forPageNotFound();
        }

        foreach ($plugin->options['settings'] as $option) {
            $optionKey = $option['key'];
            $optionValue = $this->request->getPost($optionKey);
            $plugins->setOption($pluginKey, $optionKey, $optionValue);
        }

        return redirect()->back();
    }

    public function activate(string $pluginKey): RedirectResponse
    {
        service('plugins')->activate($pluginKey);
+42 −0
Original line number Diff line number Diff line
@@ -22,3 +22,45 @@ if (! function_exists('set_plugin_option')) {
            ->set($key, $value, $context);
    }
}

if (! function_exists('array_merge_recursive_distinct')) {
    /**
     * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
     * keys to arrays rather than overwriting the value in the first array with the duplicate
     * value in the second array, as array_merge does. I.e., with array_merge_recursive,
     * this happens (documented behavior):
     *
     * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
     *     => array('key' => array('org value', 'new value'));
     *
     * array_merge_recursive_distinct does not change the datatypes of the values in the arrays.
     * Matching keys' values in the second array overwrite those in the first array, as is the
     * case with array_merge, i.e.:
     *
     * array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value'));
     *     => array('key' => array('new value'));
     *
     * Parameters are passed by reference, though only for performance reasons. They're not
     * altered by this function.
     *
     * from https://www.php.net/manual/en/function.array-merge-recursive.php#92195
     *
     * @param array<mixed> $array1
     * @param array<mixed> $array2
     * @return array<mixed>
     */
    function array_merge_recursive_distinct(array &$array1, array &$array2): array
    {
        $merged = $array1;

        foreach ($array2 as $key => &$value) {
            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
                $merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
            } else {
                $merged[$key] = $value;
            }
        }

        return $merged;
    }
}
Loading