voyager-admin/voyager

View on GitHub
src/Manager/Plugins.php

Summary

Maintainability
B
5 hrs
Test Coverage
F
51%
<?php

namespace Voyager\Admin\Manager;

use Composer\InstalledVersions;
use Illuminate\Support\{Collection, Str};
use Illuminate\Support\Facades\File;
use Voyager\Admin\Contracts\Plugins\GenericPlugin;
use Voyager\Admin\Contracts\Plugins\Features\Provider\{CSS as CSSProvider, FrontendRoutes, JS as JSProvider, MenuItems, ProtectedRoutes, Settings as SettingsProvider, Widgets as WidgetsProvider};
use Voyager\Admin\Contracts\Plugins\Features\Filter\{Layouts as LayoutFilter, Media as MediaFilter, MenuItems as MenuItemFilter, Widgets as WidgetFilter};
use Voyager\Admin\Facades\Voyager as VoyagerFacade;
class Plugins
{
    protected Collection $plugins;
    protected array $enabled_plugins = [];
    protected string $path;
    protected bool $preferences_changed = false;

    public function __construct(public Menu $menumanager, public Settings $settingsmanager)
    {
        $this->plugins = collect();
        $this->path = Str::finish(storage_path('voyager'), '/').'plugins.json';
    }

    public function setPath(string $path): void
    {
        $this->path = $path;
    }

    public function getPath(): string
    {
        return $this->path;
    }

    public function addPlugin(string|GenericPlugin $plugin): void
    {
        $start = round(microtime(true) * 1000);
        if (!$this->enabled_plugins) {
            $this->loadEnabledPlugins();
        }
        if (is_string($plugin)) {
            $plugin = new $plugin();
        }

        if (!($plugin instanceof GenericPlugin)) {
            throw new \Exception('Plugin added to Voyager has to inherit GenericPlugin');
        }

        $plugin->identifier = $plugin->repository.'@'.class_basename($plugin);
        $plugin->enabled = array_key_exists($plugin->identifier, $this->enabled_plugins);
        $plugin->version = InstalledVersions::getPrettyVersion($plugin->repository) ?? '';
        $plugin->version_normalized = InstalledVersions::getVersion($plugin->repository) ?? '';
        $plugin->stats = [];

        $plugin->preferences = new class ($plugin, $this) { // @phpstan-ignore-line
            private GenericPlugin $plugin;
            private Plugins $pluginmanager;

            public function __construct(GenericPlugin $plugin, Plugins $pluginmanager) {
                $this->plugin = $plugin;
                $this->pluginmanager = $pluginmanager;
            }

            public function set(string $key, mixed $value, ?string $locale = null): void {
                $this->pluginmanager->setPreference($this->plugin->identifier, $key, $value, $locale);
            }

            public function get(string $key, mixed $default = null, bool$translate = true): mixed {
                return $this->pluginmanager->getPreference($this->plugin->identifier, $key, $default, $translate);
            }

            public function remove(string $key): mixed {
                return $this->pluginmanager->removePreference($this->plugin->identifier, $key);
            }

            public function removeAll(): mixed {
                return $this->pluginmanager->removeAllPreferences($this->plugin->identifier);
            }
        };

        $plugin->loading_time = round(microtime(true) * 1000) - $start;

        $this->plugins->push($plugin);
    }

    public function launchPlugins(?bool $protected = null): void
    {
        $this->getAllPlugins()->each(function (GenericPlugin $plugin) use ($protected) {
            $start = round(microtime(true) * 1000);
            $plugin->stats = [
                'settings'          => 0,
                'menu_items'        => 0,
                'widgets'           => 0,
                'public_routes'     => false,
                'protected_routes'  => false,
                'js'                => false,
                'css'               => false,
                'layout_filter'     => false,
                'media_filter'      => false,
                'menu_item_filter'  => false,
                'widget_filter'     => false,

            ];
            if ($protected === true) {
                if ($plugin instanceof ProtectedRoutes) {
                    $plugin->provideProtectedRoutes();
                    $plugin->stats['protected_routes'] = true;
                }
            } elseif ($protected === false) {
                if ($plugin instanceof FrontendRoutes) {
                    $plugin->provideFrontendRoutes();
                    $plugin->stats['public_routes'] = true;
                }
            } else {
                // Register menu items
                if ($plugin instanceof MenuItems) {
                    $before = $this->menumanager->getUnfilteredItems()->count();
                    $plugin->provideMenuItems($this->menumanager);
                    $after = $this->menumanager->getUnfilteredItems()->count();

                    $plugin->stats['menu_items'] = $after - $before;
                }
                // Merge settings
                if ($plugin instanceof SettingsProvider) {
                    $this->settingsmanager->merge(
                        collect($plugin->provideSettings())->transform(function ($setting) use (&$plugin) {
                            $plugin->stats['settings']++;
                            // Transform single setting to object
                            return (object) $setting;
                        })->filter(function ($setting) {
                            // Filter out settings that are already stored
                            return !$this->settingsmanager->exists($setting->group, $setting->key);
                        })->toArray()
                    );
                }
                // Add widgets
                if ($plugin instanceof WidgetsProvider) {
                    $plugin->provideWidgets()->each(function ($widget) use (&$plugin) {
                        VoyagerFacade::addWidgets($widget);
                        $plugin->stats['widgets']++;
                    });
                }

                // Add some more stats
                $plugin->stats['js'] = $plugin instanceof JSProvider;
                $plugin->stats['css'] = $plugin instanceof CSSProvider;
                $plugin->stats['layout_filter'] = $plugin instanceof LayoutFilter;
                $plugin->stats['media_filter'] = $plugin instanceof MediaFilter;
                $plugin->stats['menu_item_filter'] = $plugin instanceof MenuItemFilter;
                $plugin->stats['widget_filter'] = $plugin instanceof WidgetFilter;
            }
            $plugin->loading_time = $plugin->loading_time + (round(microtime(true) * 1000) - $start);
        });
    }

    public function loadEnabledPlugins(): void
    {
        $this->enabled_plugins = [];

        VoyagerFacade::ensureFileExists($this->path, '[]');

        collect(VoyagerFacade::getJson(File::get($this->path), []))->where('enabled')->each(function ($plugin) {
            $this->enabled_plugins[$plugin->identifier] = [
                'preferences'   => (array) ($plugin->preferences ?? []),
            ];
        });
    }

    public function getAllPlugins(bool $enabled = true): Collection
    {
        if ($enabled) {
            return $this->plugins->where('enabled');
        }

        return $this->plugins;
    }

    public function getRegisteredPlugins(): Collection
    {
        return collect(VoyagerFacade::getJson(File::get($this->path), []))->map(function ($plugin) {
            return $plugin->identifier;
        });
    }

    public function enablePlugin(string $identifier, bool $enable = true): bool
    {
        $found = false;
        $this->getAllPlugins(false)->each(function ($plugin) use (&$found, $identifier) {
            if ($plugin->identifier == $identifier) {
                $found = true;
            }
        });

        if (!$found) {
            throw new \Exception('Plugin with identifier "'.$identifier.'" is not registered and can not be enabled/disabled!');
        }

        $plugins = collect(VoyagerFacade::getJson(File::get($this->getPath()), []));
        if (!$plugins->contains('identifier', $identifier)) {
            $plugins->push([
                'identifier' => $identifier,
                'enabled'    => $enable,
            ]);
        } else {
            $plugins->where('identifier', $identifier)->first()->enabled = $enable;
        }

        return VoyagerFacade::writeToFile($this->getPath(), json_encode($plugins, JSON_PRETTY_PRINT));
    }

    public function disablePlugin(string $identifier): bool|int
    {
        return $this->enablePlugin($identifier, false);
    }

    public function getAssets(): Collection
    {
        $assets = collect();
        $this->getAllPlugins(false)->each(function ($plugin) use ($assets) {
            if ($plugin instanceof GenericPlugin && $plugin instanceof CSSProvider) {
                $assets->push([
                    'name'      => Str::slug($plugin->name).'.css',
                    'content'   => $plugin->provideCSS()
                ]);
            }
            if ($plugin instanceof GenericPlugin && $plugin instanceof JSProvider && $plugin->enabled) {
                $assets->push([
                    'name'      => Str::slug($plugin->name).'.js',
                    'content'   => $plugin->provideJS()
                ]);
            }
        });

        return $assets;
    }

    public function setPreference(string $identifier, string $key, mixed $value, ?string $locale = null): void
    {
        if (!is_null($locale)) {
            $value = VoyagerFacade::setTranslation(
                $this->enabled_plugins[$identifier]['preferences'][$key],
                $value,
                $locale
            );
        }
        
        $this->enabled_plugins[$identifier]['preferences'][$key] = $value;
        $this->preferences_changed = true;
    }

    public function setPreferences(string $identifier, mixed $preferences): void
    {        
        $this->enabled_plugins[$identifier]['preferences'] = $preferences;
        $this->preferences_changed = true;
    }

    public function getPreference(string $identifier, string $key, mixed $default = null, bool $translate = true): mixed
    {
        $value = $this->enabled_plugins[$identifier]['preferences'][$key] ?? $default;
        if ($translate !== false) {
            return VoyagerFacade::translate($value, null);
        }

        return $value;
    }

    public function getPreferences(string $identifier): array
    {
        return $this->enabled_plugins[$identifier]['preferences'] ?? [];
    }

    public function removePreference(string $identifier, string $key): bool
    {
        // TODO: Make sure everything exists
        unset($this->enabled_plugins[$identifier]['preferences'][$key]);
        $this->preferences_changed = true;

        return true;
    }

    public function removeAllPreferences(string $identifier): bool
    {
        // TODO: Make sure everything exists
        unset($this->enabled_plugins[$identifier]['preferences']);
        $this->preferences_changed = true;

        return true;
    }

    public function cleanUninstalledPlugins(): void
    {
        $plugins = collect(VoyagerFacade::getJson(File::get($this->getPath()), []))->filter(function ($plugin) {
            return $this->getAllPlugins(false)->where('identifier', $plugin->identifier)->count() > 0;
        })->values();

        VoyagerFacade::writeToFile($this->getPath(), json_encode($plugins, JSON_PRETTY_PRINT));
    }

    public function __destruct()
    {
        if ($this->preferences_changed) {
            $plugins = collect(VoyagerFacade::getJson(File::get($this->getPath()), []))->transform(function ($plugin) {
                $plugin->preferences = $this->enabled_plugins[$plugin->identifier]['preferences'] ?? [];

                return $plugin;
            });

            VoyagerFacade::writeToFile($this->getPath(), json_encode($plugins, JSON_PRETTY_PRINT));
        }
    }
}