webseite-grav/plugins/flex-objects/flex-objects.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');
}
}
}