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');
|
||
|
}
|
||
|
}
|
||
|
}
|