526 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
		
		
			
		
	
	
			526 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
|   | <?php | ||
|  | namespace Grav\Plugin; | ||
|  | 
 | ||
|  | use Composer\Autoload\ClassLoader; | ||
|  | use Grav\Common\Debugger; | ||
|  | use Grav\Common\Grav; | ||
|  | use Grav\Common\Page\Interfaces\PageInterface; | ||
|  | use Grav\Common\Page\Types; | ||
|  | use Grav\Common\Plugin; | ||
|  | use Grav\Common\User\Interfaces\UserInterface; | ||
|  | use Grav\Events\FlexRegisterEvent; | ||
|  | use Grav\Events\PermissionsRegisterEvent; | ||
|  | use Grav\Events\PluginsLoadedEvent; | ||
|  | use Grav\Framework\Acl\PermissionsReader; | ||
|  | use Grav\Framework\Flex\FlexDirectory; | ||
|  | use Grav\Framework\Flex\Interfaces\FlexInterface; | ||
|  | use Grav\Plugin\Admin\Admin; | ||
|  | use Grav\Plugin\FlexObjects\FlexFormFactory; | ||
|  | use Grav\Plugin\Form\Forms; | ||
|  | use Grav\Plugin\FlexObjects\Admin\AdminController; | ||
|  | use Grav\Plugin\FlexObjects\Flex; | ||
|  | use RocketTheme\Toolbox\Event\Event; | ||
|  | use function is_callable; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Class FlexObjectsPlugin | ||
|  |  * @package Grav\Plugin | ||
|  |  */ | ||
|  | class FlexObjectsPlugin extends Plugin | ||
|  | { | ||
|  |     /** @var string */ | ||
|  |     protected const MIN_GRAV_VERSION = '1.7.0'; | ||
|  | 
 | ||
|  |     /** @var int[] */ | ||
|  |     public $features = [ | ||
|  |         'blueprints' => 1000, | ||
|  |     ]; | ||
|  | 
 | ||
|  |     /** @var AdminController */ | ||
|  |     protected $controller; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @return bool | ||
|  |      */ | ||
|  |     public static function checkRequirements(): bool | ||
|  |     { | ||
|  |         return version_compare(GRAV_VERSION, static::MIN_GRAV_VERSION, '>='); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @return array | ||
|  |      * | ||
|  |      * The getSubscribedEvents() gives the core a list of events | ||
|  |      *     that the plugin wants to listen to. The key of each | ||
|  |      *     array section is the event that the plugin listens to | ||
|  |      *     and the value (in the form of an array) contains the | ||
|  |      *     callable (or function) as well as the priority. The | ||
|  |      *     higher the number the higher the priority. | ||
|  |      */ | ||
|  |     public static function getSubscribedEvents(): array | ||
|  |     { | ||
|  |         if (!static::checkRequirements()) { | ||
|  |             return []; | ||
|  |         } | ||
|  | 
 | ||
|  |         return [ | ||
|  |             PluginsLoadedEvent::class => [ | ||
|  |                 ['initializeFlex', 10] | ||
|  |             ], | ||
|  |             PermissionsRegisterEvent::class => [ | ||
|  |                 ['onRegisterPermissions', 100] | ||
|  |             ], | ||
|  |             FlexRegisterEvent::class => [ | ||
|  |                 ['onRegisterFlex', 100] | ||
|  |             ], | ||
|  |             'onCliInitialize' => [ | ||
|  |                 ['autoload', 100000], | ||
|  |                 ['initializeFlex', 10] | ||
|  |             ], | ||
|  |             'onPluginsInitialized' => [ | ||
|  |                 ['onPluginsInitialized', 0], | ||
|  |             ], | ||
|  |             'onFormRegisterTypes' => [ | ||
|  |                 ['onFormRegisterTypes', 0] | ||
|  |             ] | ||
|  |         ]; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * [PluginsLoadedEvent:100000] Composer autoload. | ||
|  |      * | ||
|  |      * @return ClassLoader | ||
|  |      */ | ||
|  |     public function autoload(): ClassLoader | ||
|  |     { | ||
|  |         return require __DIR__ . '/vendor/autoload.php'; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * [PluginsLoadedEvent:10]: Initialize Flex | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function initializeFlex(): void | ||
|  |     { | ||
|  |         $config = $this->config->get('plugins.flex-objects.directories') ?? []; | ||
|  | 
 | ||
|  |         // Add to DI container
 | ||
|  |         $this->grav['flex_objects'] = static function (Grav $grav) use ($config) { | ||
|  |             /** @var FlexInterface $flex */ | ||
|  |             $flex = $grav['flex']; | ||
|  | 
 | ||
|  |             $flexObjects = new Flex($flex, $config); | ||
|  | 
 | ||
|  |             // This event is for backwards compatibility only, do not use it!
 | ||
|  |             $grav->fireEvent('onFlexInit', new Event(['flex' => $flexObjects])); | ||
|  | 
 | ||
|  |             return $flexObjects; | ||
|  |         }; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Initialize the plugin | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onPluginsInitialized(): void | ||
|  |     { | ||
|  |         if ($this->isAdmin()) { | ||
|  |             /** @var UserInterface|null $user */ | ||
|  |             $user = $this->grav['user'] ?? null; | ||
|  | 
 | ||
|  |             if (null === $user || !$user->authorize('login', 'admin')) { | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             $this->enable([ | ||
|  |                 'onAdminTwigTemplatePaths' => [ | ||
|  |                     ['onAdminTwigTemplatePaths', 10] | ||
|  |                 ], | ||
|  |                 'onAdminMenu' => [ | ||
|  |                     ['onAdminMenu', 0] | ||
|  |                 ], | ||
|  |                 'onAdminPage' => [ | ||
|  |                     ['onAdminPage', 0] | ||
|  |                 ], | ||
|  |                 'onAdminCompilePresetSCSS' => [ | ||
|  |                     ['onAdminCompilePresetSCSS', 0] | ||
|  |                 ], | ||
|  |                 'onDataTypeExcludeFromDataManagerPluginHook' => [ | ||
|  |                     ['onDataTypeExcludeFromDataManagerPluginHook', 0] | ||
|  |                 ], | ||
|  |                 'onAdminControllerInit' => [ | ||
|  |                     ['onAdminControllerInit', 0] | ||
|  |                 ], | ||
|  |                 'onThemeInitialized' => [ | ||
|  |                     ['onThemeInitialized', 0] | ||
|  |                 ], | ||
|  |                 'onPageInitialized' => [ | ||
|  |                     ['onAdminPageInitialized', 0] | ||
|  |                 ], | ||
|  |                 'onTwigSiteVariables' => [ | ||
|  |                     ['onTwigAdminVariables', 0] | ||
|  |                 ], | ||
|  |                 'onGetPageTemplates' => | ||
|  |                     ['onGetPageTemplates', 0] | ||
|  | 
 | ||
|  |             ]); | ||
|  | 
 | ||
|  |         } else { | ||
|  |             $this->enable([ | ||
|  |                 'onTwigTemplatePaths' => [ | ||
|  |                     ['onTwigTemplatePaths', 0] | ||
|  |                 ], | ||
|  |             ]); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param FlexRegisterEvent $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onRegisterFlex(FlexRegisterEvent $event): void | ||
|  |     { | ||
|  |         /** @var \Grav\Framework\Flex\Flex $flex */ | ||
|  |         $flex = $event->flex; | ||
|  |         $types = (array)$this->config->get('plugins.flex-objects.directories', []); | ||
|  |         $this->registerDirectories($flex, $types); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onThemeInitialized(): void | ||
|  |     { | ||
|  |         // Register directories defined in the theme.
 | ||
|  |         /** @var \Grav\Framework\Flex\Flex $flex */ | ||
|  |         $flex = $this->grav['flex']; | ||
|  |         $types = (array)$this->config->get('plugins.flex-objects.directories', []); | ||
|  |         $this->registerDirectories($flex, $types, true); | ||
|  | 
 | ||
|  |         /** @var AdminController controller */ | ||
|  |         $this->controller = new AdminController(); | ||
|  | 
 | ||
|  |         /** @var Debugger $debugger */ | ||
|  |         $debugger = Grav::instance()['debugger']; | ||
|  |         $names = implode(', ', array_keys($flex->getDirectories())); | ||
|  |         $debugger->addMessage(sprintf('Registered flex types: %s', $names), 'debug'); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param \Grav\Framework\Flex\Flex $flex | ||
|  |      * @param array $types | ||
|  |      * @param bool $report | ||
|  |      */ | ||
|  |     protected function registerDirectories(\Grav\Framework\Flex\Flex $flex, array $types, bool $report = false): void | ||
|  |     { | ||
|  |         $map = Flex::getLegacyBlueprintMap(false); | ||
|  |         foreach ($types as $blueprint) { | ||
|  |             // Backwards compatibility to v1.0.0-rc.3
 | ||
|  |             $blueprint = $map[$blueprint] ?? $blueprint; | ||
|  |             $type = basename((string)$blueprint, '.yaml'); | ||
|  |             if (!$type) { | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (!file_exists($blueprint)) { | ||
|  |                 if ($report) { | ||
|  |                     /** @var Debugger $debugger */ | ||
|  |                     $debugger = Grav::instance()['debugger']; | ||
|  |                     $debugger->addMessage(sprintf('Flex: blueprint for flex type %s is missing', $type), 'error'); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             $directory = $flex->getDirectory($type); | ||
|  |             if (!$directory || !$directory->isEnabled()) { | ||
|  |                 $flex->addDirectoryType($type, $blueprint); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Initial stab at registering permissions (WIP) | ||
|  |      * | ||
|  |      * @param PermissionsRegisterEvent $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onRegisterPermissions(PermissionsRegisterEvent $event): void | ||
|  |     { | ||
|  |         /** @var Flex $flex */ | ||
|  |         $flex = $this->grav['flex_objects']; | ||
|  |         $directories = $flex->getDirectories(); | ||
|  | 
 | ||
|  |         $permissions = $event->permissions; | ||
|  | 
 | ||
|  |         $actions = []; | ||
|  |         foreach ($directories as $directory) { | ||
|  |             $data = $directory->getConfig('admin.permissions', []); | ||
|  |             $actions[] = PermissionsReader::fromArray($data, $permissions->getTypes()); | ||
|  | 
 | ||
|  |         } | ||
|  |         $actions[] = PermissionsReader::fromYaml("plugin://{$this->name}/permissions.yaml"); | ||
|  | 
 | ||
|  |         $permissions->addActions(array_replace(...$actions)); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param Event $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onFormRegisterTypes(Event $event): void | ||
|  |     { | ||
|  |         /** @var Forms $forms */ | ||
|  |         $forms = $event['forms']; | ||
|  |         $forms->registerType('flex', new FlexFormFactory()); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param Event $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onAdminPage(Event $event): void | ||
|  |     { | ||
|  |         if ($this->controller->isActive()) { | ||
|  |             $event->stopPropagation(); | ||
|  | 
 | ||
|  |             /** @var PageInterface $page */ | ||
|  |             $page = $event['page']; | ||
|  |             $page->init(new \SplFileInfo(__DIR__ . '/admin/pages/flex-objects.md')); | ||
|  |             $page->slug($this->controller->getLocation()); | ||
|  |             $header = $page->header(); | ||
|  |             $header->access = ['admin.login']; | ||
|  |             $header->controller = $this->controller->getInfo(); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * [onPageInitialized:0]: Run controller | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onAdminPageInitialized(): void | ||
|  |     { | ||
|  |         if ($this->controller->isActive()) { | ||
|  |             $this->controller->execute(); | ||
|  |             $this->controller->redirect(); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param Event $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onAdminControllerInit(Event $event): void | ||
|  |     { | ||
|  |         $eventController = $event['controller']; | ||
|  | 
 | ||
|  |         // Blacklist all admin routes, including aliases and redirects.
 | ||
|  |         $eventController->blacklist_views[] = 'flex-objects'; | ||
|  |         foreach ($this->controller->getAdminRoutes() as $route => $info) { | ||
|  |             $eventController->blacklist_views[] = trim($route, '/'); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Add Flex-Object's preset.scss to the Admin Preset SCSS compile process | ||
|  |      * | ||
|  |      * @param Event $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onAdminCompilePresetSCSS(Event $event): void | ||
|  |     { | ||
|  |         $event['scss']->add($this->grav['locator']->findResource('plugins://flex-objects/scss/_preset.scss')); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param Event $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onGetPageTemplates(Event $event): void | ||
|  |     { | ||
|  |         /** @var Types $types */ | ||
|  |         $types = $event->types; | ||
|  |         $types->register('flex-objects', 'plugins://flex-objects/blueprints/pages/flex-objects.yaml'); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Form select options listing all enabled directories. | ||
|  |      * | ||
|  |      * @return array | ||
|  |      */ | ||
|  |     public static function directoryOptions(): array | ||
|  |     { | ||
|  |         /** @var Flex $flex */ | ||
|  |         $flex = Grav::instance()['flex_objects']; | ||
|  |         $directories = $flex->getDirectories(); | ||
|  | 
 | ||
|  |         $list = []; | ||
|  |         /** | ||
|  |          * @var string $type | ||
|  |          * @var FlexDirectory $directory | ||
|  |          */ | ||
|  |         foreach ($directories as $type => $directory) { | ||
|  |             if (!$directory->getConfig('site.hidden')) { | ||
|  |                 $list[$type] = $directory->getTitle(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return $list; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @return array | ||
|  |      */ | ||
|  |     public function getAdminMenu(): array | ||
|  |     { | ||
|  |         /** @var Flex $flex */ | ||
|  |         $flex = $this->grav['flex_objects']; | ||
|  | 
 | ||
|  |         $list = []; | ||
|  |         foreach ($flex->getAdminMenuItems() as $name => $item) { | ||
|  |             $route = trim($item['route'] ?? $name, '/'); | ||
|  |             $list[$route] = $item; | ||
|  |         } | ||
|  | 
 | ||
|  |         return $list; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Add Flex Directory to admin menu | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onAdminMenu(): void | ||
|  |     { | ||
|  |         /** @var Flex $flex */ | ||
|  |         $flex = $this->grav['flex_objects']; | ||
|  |         /** @var Admin $admin */ | ||
|  |         $admin = $this->grav['admin']; | ||
|  | 
 | ||
|  |         foreach ($this->getAdminMenu() as $route => $item) { | ||
|  |             $directory = null; | ||
|  |             if (isset($item['directory'])) { | ||
|  |                 $directory = $flex->getDirectory($item['directory']); | ||
|  |                 if (!$directory || !$directory->isEnabled()) { | ||
|  |                     continue; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             $title = $item['title'] ?? 'PLUGIN_FLEX_OBJECTS.TITLE'; | ||
|  |             $index = $item['index'] ?? 0; | ||
|  |             if (($this->grav['twig']->plugins_hooked_nav[$title]['index'] ?? 1000) <= $index) { | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             $location = $item['location'] ?? $route; | ||
|  |             $hidden = $item['hidden'] ?? false; | ||
|  |             $icon = $item['icon'] ?? 'fa-list'; | ||
|  |             $authorize = $item['authorize'] ?? ($directory ? null : ['admin.flex-objects', 'admin.super']); | ||
|  |             if ($hidden || (null === $authorize && $directory->isAuthorized('list', 'admin', $admin->user))) { | ||
|  |                 continue; | ||
|  |             } | ||
|  |             $cache = $directory ? $directory->getCache('index') : null; | ||
|  |             $count = $cache ? $cache->get('admin-count-' . md5($admin->user->username)) : false; | ||
|  |             if (null === $count) { | ||
|  |                 try { | ||
|  |                     $collection = $directory->getCollection(); | ||
|  |                     if (is_callable([$collection, 'isAuthorized'])) { | ||
|  |                         $count = $collection->isAuthorized('list', 'admin', $admin->user)->count(); | ||
|  |                     } else { | ||
|  |                         $count = $collection->count(); | ||
|  |                     } | ||
|  |                     $cache->set('admin-count-' . md5($admin->user->username), $count); | ||
|  |                 } catch (\InvalidArgumentException $e) { | ||
|  |                     continue; | ||
|  |                 } | ||
|  |             } | ||
|  |             $badge = $directory ? ['badge' => ['count' => $count]] : []; | ||
|  |             $priority = $item['priority'] ?? 0; | ||
|  | 
 | ||
|  |             $this->grav['twig']->plugins_hooked_nav[$title] = [ | ||
|  |                 'location' => $location, | ||
|  |                 'route' => $route, | ||
|  |                 'index' => $index, | ||
|  |                 'icon' => $icon, | ||
|  |                 'authorize' => $authorize, | ||
|  |                 'priority' => $priority | ||
|  |             ] + $badge; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Exclude Flex Directory data from the Data Manager plugin | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onDataTypeExcludeFromDataManagerPluginHook(): void | ||
|  |     { | ||
|  |         $this->grav['admin']->dataTypesExcludedFromDataManagerPlugin[] = 'flex-objects'; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Add current directory to twig lookup paths. | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onTwigTemplatePaths(): void | ||
|  |     { | ||
|  |         $extra_site_twig_path = $this->config->get('plugins.flex-objects.extra_site_twig_path'); | ||
|  |         $extra_path = $extra_site_twig_path ? $this->grav['locator']->findResource($extra_site_twig_path) : null; | ||
|  |         if ($extra_path) { | ||
|  |             $this->grav['twig']->twig_paths[] = $extra_path; | ||
|  |         } | ||
|  | 
 | ||
|  |         $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Add plugin templates path | ||
|  |      * | ||
|  |      * @param Event $event | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onAdminTwigTemplatePaths(Event $event): void | ||
|  |     { | ||
|  |         $extra_admin_twig_path = $this->config->get('plugins.flex-objects.extra_admin_twig_path'); | ||
|  |         $extra_path = $extra_admin_twig_path ? $this->grav['locator']->findResource($extra_admin_twig_path) : null; | ||
|  | 
 | ||
|  |         $paths = $event['paths']; | ||
|  |         if ($extra_path) { | ||
|  |             $paths[] = $extra_path; | ||
|  |         } | ||
|  | 
 | ||
|  |         $paths[] = __DIR__ . '/admin/templates'; | ||
|  |         $event['paths'] = $paths; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Set needed variables to display directory. | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function onTwigAdminVariables(): void | ||
|  |     { | ||
|  |         if ($this->controller->isActive()) { | ||
|  |             // Twig shortcuts
 | ||
|  |             $this->grav['twig']->twig_vars['controller'] = $this->controller; | ||
|  |             $this->grav['twig']->twig_vars['action'] = $this->controller->getAction(); | ||
|  |             $this->grav['twig']->twig_vars['task'] = $this->controller->getTask(); | ||
|  |             $this->grav['twig']->twig_vars['target'] = $this->controller->getTarget(); | ||
|  |             $this->grav['twig']->twig_vars['key'] = $this->controller->getId(); | ||
|  | 
 | ||
|  |             $this->grav['twig']->twig_vars['flex'] = $this->grav['flex_objects']; | ||
|  |             $this->grav['twig']->twig_vars['directory'] = $this->controller->getDirectory(); | ||
|  |             $this->grav['twig']->twig_vars['collection'] = $this->controller->getCollection(); | ||
|  |             $this->grav['twig']->twig_vars['object'] = $this->controller->getObject(); | ||
|  | 
 | ||
|  |             // CSS / JS Assets
 | ||
|  |             $this->grav['assets']->addCss('plugin://flex-objects/css/admin.css'); | ||
|  |             $this->grav['assets']->addCss('plugin://admin/themes/grav/css/codemirror/codemirror.css'); | ||
|  |         } | ||
|  |     } | ||
|  | } |