1778 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			1778 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php
 | |
| 
 | |
| namespace Grav\Plugin\FlexObjects\Admin;
 | |
| 
 | |
| use Exception;
 | |
| use Grav\Common\Cache;
 | |
| use Grav\Common\Config\Config;
 | |
| use Grav\Common\Debugger;
 | |
| use Grav\Common\Filesystem\Folder;
 | |
| use Grav\Common\Flex\Types\Pages\PageCollection;
 | |
| use Grav\Common\Flex\Types\Pages\PageIndex;
 | |
| use Grav\Common\Flex\Types\Pages\PageObject;
 | |
| use Grav\Common\Grav;
 | |
| use Grav\Common\Language\Language;
 | |
| use Grav\Common\Page\Interfaces\PageInterface;
 | |
| use Grav\Common\Uri;
 | |
| use Grav\Common\User\Interfaces\UserInterface;
 | |
| use Grav\Common\Utils;
 | |
| use Grav\Framework\Controller\Traits\ControllerResponseTrait;
 | |
| use Grav\Framework\File\Formatter\CsvFormatter;
 | |
| use Grav\Framework\File\Formatter\YamlFormatter;
 | |
| use Grav\Framework\File\Interfaces\FileFormatterInterface;
 | |
| use Grav\Framework\Flex\FlexForm;
 | |
| use Grav\Framework\Flex\FlexFormFlash;
 | |
| use Grav\Framework\Flex\FlexObject;
 | |
| use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
 | |
| use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
 | |
| use Grav\Framework\Flex\Interfaces\FlexDirectoryInterface;
 | |
| use Grav\Framework\Flex\Interfaces\FlexFormInterface;
 | |
| use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
 | |
| use Grav\Framework\Flex\Interfaces\FlexTranslateInterface;
 | |
| use Grav\Framework\Object\Interfaces\ObjectInterface;
 | |
| use Grav\Framework\Psr7\Response;
 | |
| use Grav\Framework\RequestHandler\Exception\RequestException;
 | |
| use Grav\Framework\Route\Route;
 | |
| use Grav\Framework\Route\RouteFactory;
 | |
| use Grav\Plugin\Admin\Admin;
 | |
| use Grav\Plugin\FlexObjects\Controllers\MediaController;
 | |
| use Grav\Plugin\FlexObjects\Flex;
 | |
| use Nyholm\Psr7\ServerRequest;
 | |
| use Psr\Http\Message\ResponseInterface;
 | |
| use Psr\Http\Message\ServerRequestInterface;
 | |
| use RocketTheme\Toolbox\Event\Event;
 | |
| use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
 | |
| use RocketTheme\Toolbox\Session\Message;
 | |
| use RuntimeException;
 | |
| use function dirname;
 | |
| use function in_array;
 | |
| use function is_array;
 | |
| use function is_callable;
 | |
| 
 | |
| /**
 | |
|  * Class AdminController
 | |
|  * @package Grav\Plugin\FlexObjects
 | |
|  */
 | |
| class AdminController
 | |
| {
 | |
|     use ControllerResponseTrait;
 | |
| 
 | |
|     /** @var AdminController|null */
 | |
|     private static $instance;
 | |
| 
 | |
|     /** @var Grav */
 | |
|     public $grav;
 | |
|     /** @var string */
 | |
|     public $view;
 | |
|     /** @var string */
 | |
|     public $task;
 | |
|     /** @var Route|null */
 | |
|     public $route;
 | |
|     /** @var array */
 | |
|     public $post;
 | |
|     /** @var array|null */
 | |
|     public $data;
 | |
| 
 | |
|     /** @var array */
 | |
|     protected $adminRoutes;
 | |
|     /** @var Uri */
 | |
|     protected $uri;
 | |
|     /** @var Admin */
 | |
|     protected $admin;
 | |
|     /** @var UserInterface */
 | |
|     protected $user;
 | |
|     /** @var string */
 | |
|     protected $redirect;
 | |
|     /** @var int */
 | |
|     protected $redirectCode;
 | |
|     /** @var Route */
 | |
|     protected $currentRoute;
 | |
|     /** @var Route */
 | |
|     protected $referrerRoute;
 | |
| 
 | |
|     /** @var string|null */
 | |
|     protected $action;
 | |
|     /** @var string|null */
 | |
|     protected $location;
 | |
|     /** @var string|null */
 | |
|     protected $target;
 | |
|     /** @var string|null */
 | |
|     protected $id;
 | |
|     /** @var bool */
 | |
|     protected $active;
 | |
|     /** @var FlexObjectInterface|false|null */
 | |
|     protected $object;
 | |
|     /** @var FlexCollectionInterface|null */
 | |
|     protected $collection;
 | |
|     /** @var FlexDirectoryInterface|null */
 | |
|     protected $directory;
 | |
| 
 | |
|     /** @var string */
 | |
|     protected $nonce_name = 'admin-nonce';
 | |
|     /** @var string */
 | |
|     protected $nonce_action = 'admin-form';
 | |
|     /** @var string */
 | |
|     protected $task_prefix = 'task';
 | |
|     /** @var string */
 | |
|     protected $action_prefix = 'action';
 | |
| 
 | |
|     /**
 | |
|      * Unknown task, call onFlexTask[NAME] event.
 | |
|      *
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskDefault(): bool
 | |
|     {
 | |
|         $object = $this->getObject();
 | |
|         $type = $this->target;
 | |
|         $key = $this->id;
 | |
| 
 | |
|         $directory = $this->getDirectory($type);
 | |
| 
 | |
|         if ($object && $object->exists()) {
 | |
|             $event = new Event(
 | |
|                 [
 | |
|                     'type' => $type,
 | |
|                     'key' => $key,
 | |
|                     'admin' => $this->admin,
 | |
|                     'flex' => $this->getFlex(),
 | |
|                     'directory' => $directory,
 | |
|                     'object' => $object,
 | |
|                     'data' => $this->data,
 | |
|                     'redirect' => $this->redirect
 | |
|                 ]
 | |
|             );
 | |
| 
 | |
|             try {
 | |
|                 $grav = Grav::instance();
 | |
|                 $grav->fireEvent('onFlexTask' . ucfirst($this->task), $event);
 | |
|             } catch (Exception $e) {
 | |
|                 /** @var Debugger $debugger */
 | |
|                 $debugger = $this->grav['debugger'];
 | |
|                 $debugger->addException($e);
 | |
|                 $this->admin->setMessage($e->getMessage(), 'error');
 | |
|             }
 | |
| 
 | |
|             $redirect = $event['redirect'];
 | |
|             if ($redirect) {
 | |
|                 $this->setRedirect($redirect);
 | |
|             }
 | |
| 
 | |
|             return $event->isPropagationStopped();
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Default action, onFlexAction[NAME] event.
 | |
|      *
 | |
|      * @return bool
 | |
|      */
 | |
|     public function actionDefault(): bool
 | |
|     {
 | |
|         $object = $this->getObject();
 | |
|         $type = $this->target;
 | |
|         $key = $this->id;
 | |
| 
 | |
|         $directory = $this->getDirectory($type);
 | |
| 
 | |
|         if ($object && $object->exists()) {
 | |
|             $event = new Event(
 | |
|                 [
 | |
|                     'type' => $type,
 | |
|                     'key' => $key,
 | |
|                     'admin' => $this->admin,
 | |
|                     'flex' => $this->getFlex(),
 | |
|                     'directory' => $directory,
 | |
|                     'object' => $object,
 | |
|                     'redirect' => $this->redirect
 | |
|                 ]
 | |
|             );
 | |
| 
 | |
|             try {
 | |
|                 $grav = Grav::instance();
 | |
|                 $grav->fireEvent('onFlexAction' . ucfirst($this->action), $event);
 | |
|             } catch (Exception $e) {
 | |
|                 /** @var Debugger $debugger */
 | |
|                 $debugger = $this->grav['debugger'];
 | |
|                 $debugger->addException($e);
 | |
|                 $this->admin->setMessage($e->getMessage(), 'error');
 | |
|             }
 | |
| 
 | |
|             $redirect = $event['redirect'];
 | |
|             if ($redirect) {
 | |
|                 $this->setRedirect($redirect);
 | |
|             }
 | |
| 
 | |
|             return $event->isPropagationStopped();
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get datatable for list view.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function actionList(): void
 | |
|     {
 | |
|         /** @var Uri $uri */
 | |
|         $uri = $this->grav['uri'];
 | |
|         if ($uri->extension() === 'json') {
 | |
|             $directory = $this->getDirectory();
 | |
| 
 | |
|             $options = [
 | |
|                 'collection' => $this->getCollection(),
 | |
|                 'url' => $uri->path(),
 | |
|                 'page' => $uri->query('page'),
 | |
|                 'limit' => $uri->query('per_page'),
 | |
|                 'sort' => $uri->query('sort'),
 | |
|                 'search' => $uri->query('filter'),
 | |
|                 'filters' => $uri->query('filters'),
 | |
|             ];
 | |
| 
 | |
|             $table = $this->getFlex()->getDataTable($directory, $options);
 | |
| 
 | |
|             $response = $this->createJsonResponse($table->jsonSerialize());
 | |
| 
 | |
|             $this->close($response);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Alias for Export action.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function actionCsv(): void
 | |
|     {
 | |
|         $this->actionExport();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Export action. Defaults to CVS export.
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function actionExport(): void
 | |
|     {
 | |
|         $collection = $this->getCollection();
 | |
|         if (!$collection) {
 | |
|             throw new RuntimeException('Internal Error', 500);
 | |
|         }
 | |
|         if (is_callable([$collection, 'isAuthorized']) && !$collection->isAuthorized('list', 'admin', $this->user)) {
 | |
|             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' list.', 403);
 | |
|         }
 | |
| 
 | |
|         $config = $collection->getFlexDirectory()->getConfig('admin.views.export') ?? $collection->getFlexDirectory()->getConfig('admin.export') ?? false;
 | |
|         if (!$config || empty($config['enabled'])) {
 | |
|             throw new RuntimeException($this->admin::translate('Not Found'), 404);
 | |
|         }
 | |
| 
 | |
|         $defaultFormatter = CsvFormatter::class;
 | |
|         $class = trim($config['formatter']['class'] ?? $defaultFormatter, '\\');
 | |
|         $method = $config['method'] ?? ($class === $defaultFormatter ? 'csvSerialize' : 'jsonSerialize');
 | |
|         if (!class_exists($class)) {
 | |
|             throw new RuntimeException($this->admin::translate('Formatter Not Found'), 404);
 | |
|         }
 | |
|         /** @var FileFormatterInterface $formatter */
 | |
|         $formatter = new $class($config['formatter']['options'] ?? []);
 | |
|         $filename = ($config['filename'] ?? 'export') . $formatter->getDefaultFileExtension();
 | |
| 
 | |
|         if (method_exists($collection, $method)) {
 | |
|             $list = $collection->{$method}();
 | |
|         } else {
 | |
|             $list = [];
 | |
| 
 | |
|             /** @var ObjectInterface $object */
 | |
|             foreach ($collection as $object) {
 | |
|                 if (method_exists($object, $method)) {
 | |
|                     $data = $object->{$method}();
 | |
|                     if ($data) {
 | |
|                         $list[] = $data;
 | |
|                     }
 | |
|                 } else {
 | |
|                     $list[] = $object->jsonSerialize();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $response = new Response(
 | |
|             200,
 | |
|             [
 | |
|                 'Content-Type' => $formatter->getMimeType(),
 | |
|                 'Content-Disposition' => 'inline; filename="' . $filename . '"',
 | |
|                 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
 | |
|                 'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
 | |
|                 'Cache-Control' => 'no-store, no-cache, must-revalidate',
 | |
|                 'Pragma' => 'no-cache',
 | |
|             ],
 | |
|             $formatter->encode($list)
 | |
|         );
 | |
| 
 | |
|         $this->close($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Delete object from directory.
 | |
|      *
 | |
|      * @return ObjectInterface|bool
 | |
|      */
 | |
|     public function taskDelete()
 | |
|     {
 | |
|         $object = null;
 | |
|         try {
 | |
|             $object = $this->getObject();
 | |
| 
 | |
|             if ($object && $object->exists()) {
 | |
|                 if ($object instanceof FlexAuthorizeInterface && !$object->isAuthorized('delete', 'admin', $this->user)) {
 | |
|                     throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' delete.', 403);
 | |
|                 }
 | |
| 
 | |
|                 $object->delete();
 | |
| 
 | |
|                 $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_DELETE_SUCCESS'));
 | |
|                 if ($this->currentRoute->withoutGravParams()->getRoute() === $this->referrerRoute->getRoute()) {
 | |
|                     $redirect = dirname($this->currentRoute->withoutGravParams()->toString(true));
 | |
|                 } else {
 | |
|                     $redirect = $this->referrerRoute->toString(true);
 | |
|                 }
 | |
| 
 | |
|                 $this->setRedirect($redirect);
 | |
| 
 | |
|                 $grav = Grav::instance();
 | |
|                 $grav->fireEvent('onFlexAfterDelete', new Event(['type' => 'flex', 'object' => $object]));
 | |
|             }
 | |
|         } catch (RuntimeException $e) {
 | |
|             $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_DELETE_FAILURE', $e->getMessage()), 'error');
 | |
| 
 | |
|             $this->setRedirect($this->referrerRoute->toString(true), 302);
 | |
|         }
 | |
| 
 | |
|         return $object !== null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a new empty folder (from modal).
 | |
|      *
 | |
|      * TODO: Move pages specific logic
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function taskSaveNewFolder(): void
 | |
|     {
 | |
|         $directory = $this->getDirectory();
 | |
|         if (!$directory) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         if ($directory instanceof FlexAuthorizeInterface && !$directory->isAuthorized('create', 'admin', $this->user)) {
 | |
|             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.', 403);
 | |
|         }
 | |
| 
 | |
|         $collection = $directory->getCollection();
 | |
|         if (!($collection instanceof PageCollection || $collection instanceof PageIndex)) {
 | |
|             throw new RuntimeException('Task saveNewFolder works only for pages', 400);
 | |
|         }
 | |
| 
 | |
|         $data = $this->data;
 | |
|         $route = trim($data['route'] ?? '', '/');
 | |
| 
 | |
|         // TODO: Folder name needs to be validated!
 | |
|         $folder = mb_strtolower($data['folder'] ?? '');
 | |
|         if ($folder === '' || mb_strpos($folder, '/') !== false) {
 | |
|             throw new RuntimeException('Creating folder failed, bad folder name', 400);
 | |
|         }
 | |
| 
 | |
|         $parent = $route ? $directory->getObject($route) : $collection->getRoot();
 | |
|         if (!$parent instanceof PageObject) {
 | |
|             throw new RuntimeException('Creating folder failed, bad parent route', 400);
 | |
|         }
 | |
| 
 | |
|         $path = $parent->getFlexDirectory()->getStorageFolder($parent->getStorageKey());
 | |
|         if (!$path) {
 | |
|             throw new RuntimeException('Creating folder failed, bad parent storage path', 400);
 | |
|         }
 | |
| 
 | |
|         // Ordering
 | |
|         $orders = $parent->children()->visible()->getProperty('order');
 | |
|         $maxOrder = 0;
 | |
|         foreach ($orders as $order) {
 | |
|             $maxOrder = max($maxOrder, (int)$order);
 | |
|         }
 | |
| 
 | |
|         $orderOfNewFolder = $maxOrder ? sprintf('%02d.', $maxOrder+1) : '';
 | |
|         $new_path         = $path . '/' . $orderOfNewFolder . $folder;
 | |
| 
 | |
|         /** @var UniformResourceLocator $locator */
 | |
|         $locator = $this->grav['locator'];
 | |
|         if ($locator->isStream($new_path)) {
 | |
|             $new_path = $locator->findResource($new_path, true, true);
 | |
|         } else {
 | |
|             $new_path = GRAV_ROOT . '/' . $new_path;
 | |
|         }
 | |
| 
 | |
|         Folder::create($new_path);
 | |
|         Cache::clearCache('invalidate');
 | |
| 
 | |
|         $this->grav->fireEvent('onAdminAfterSaveAs', new Event(['path' => $new_path]));
 | |
| 
 | |
|         $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_NEW_FOLDER_SUCCESS'));
 | |
| 
 | |
|         $this->setRedirect($this->referrerRoute->toString(true));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a new object (from modal).
 | |
|      *
 | |
|      * TODO: Move pages specific logic
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function taskContinue(): void
 | |
|     {
 | |
|         $directory = $this->getDirectory();
 | |
|         if (!$directory) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $this->data['route'] = '/' . trim($this->data['route'] ?? '', '/');
 | |
|         $route = trim($this->data['route'], '/');
 | |
| 
 | |
|         $object = $this->getObject($route);
 | |
|         $authorized = $object && $object->isAuthorized('create', 'admin', $this->user);
 | |
| 
 | |
|         if (!$authorized && !$directory->isAuthorized('create', 'admin', $this->user)) {
 | |
|             $this->setRedirect($this->referrerRoute->toString(true));
 | |
| 
 | |
|             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' add.', 403);
 | |
|         }
 | |
| 
 | |
|         $folder = $this->data['folder'] ?? null;
 | |
|         $title = $this->data['title'] ?? null;
 | |
|         if ($title) {
 | |
|             $this->data['header']['title'] = $this->data['title'];
 | |
|             unset($this->data['title']);
 | |
|         }
 | |
|         if (null !== $folder && 0 === strpos($folder, '@slugify-')) {
 | |
|             $folder = \Grav\Plugin\Admin\Utils::slug($this->data[substr($folder, 9)] ?? '');
 | |
|         }
 | |
|         if (!$folder) {
 | |
|             $folder = \Grav\Plugin\Admin\Utils::slug($title) ?: '';
 | |
|         }
 | |
|         $folder = ltrim($folder, '_');
 | |
|         if ($folder === '' || mb_strpos($folder, '/') !== false) {
 | |
|             throw new RuntimeException('Creating page failed: bad folder name', 400);
 | |
|         }
 | |
| 
 | |
|         if (isset($this->data['name']) && strpos($this->data['name'], 'modular/') === 0) {
 | |
|             $this->data['header']['body_classes'] = 'modular';
 | |
|             $folder = '_' . $folder;
 | |
|         }
 | |
|         $this->data['folder'] = $folder;
 | |
| 
 | |
|         unset($this->data['blueprint']);
 | |
|         $key = trim("{$route}/{$folder}", '/');
 | |
|         if ($directory->getObject($key)) {
 | |
|             throw new RuntimeException("Page '/{$key}' already exists!", 403);
 | |
|         }
 | |
| 
 | |
|         $max = 0;
 | |
|         if (isset($this->data['visible'])) {
 | |
|             $auto = $this->data['visible'] === '';
 | |
|             $visible = (bool)($this->data['visible'] ?? false);
 | |
|             unset($this->data['visible']);
 | |
| 
 | |
|             // Empty string on visible means auto.
 | |
|             if ($auto || $visible) {
 | |
|                 /** @var PageObject $parent */
 | |
|                 $parent = $route ? $directory->getObject($route) : $directory->getIndex()->getRoot();
 | |
|                 $children = $parent ? $parent->children()->visible() : [];
 | |
|                 $max = $auto ? 0 : 1;
 | |
|                 foreach ($children as $child) {
 | |
|                     $max = max($max, (int)$child->order());
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             $this->data['order'] = $max ? $max + 1 : false;
 | |
|         }
 | |
| 
 | |
|         $this->data['lang'] = $this->getLanguage();
 | |
| 
 | |
|         $header = $this->data['header'] ?? [];
 | |
|         $this->grav->fireEvent('onAdminCreatePageFrontmatter', new Event(['header' => &$header,
 | |
|             'data' => $this->data]));
 | |
| 
 | |
|         $formatter = new YamlFormatter();
 | |
|         $this->data['frontmatter'] = $formatter->encode($header);
 | |
|         $this->data['header'] = $header;
 | |
| 
 | |
|         $this->object = $directory->createObject($this->data, $key);
 | |
| 
 | |
|         // Reset form, we are starting from scratch.
 | |
|         /** @var FlexForm $form */
 | |
|         $form = $this->object->getForm('', ['reset' => true]);
 | |
| 
 | |
|         /** @var FlexFormFlash $flash */
 | |
|         $flash = $form->getFlash();
 | |
|         $flash->setUrl($this->getFlex()->adminRoute($this->object));
 | |
|         $flash->save(true);
 | |
| 
 | |
|         // Store the name and route of a page, to be used pre-filled defaults of the form in the future
 | |
|         $this->admin->session()->lastPageName  = $this->data['name'] ?? '';
 | |
|         $this->admin->session()->lastPageRoute = $this->data['route'] ?? '';
 | |
| 
 | |
|         $this->setRedirect($flash->getUrl());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Save page as a new copy.
 | |
|      *
 | |
|      * Route: /pages
 | |
|      *
 | |
|      * @return bool True if the action was performed.
 | |
|      * @throws RuntimeException
 | |
|      */
 | |
|     protected function taskCopy(): bool
 | |
|     {
 | |
|         try {
 | |
|             $object = $this->getObject();
 | |
|             if (!$object || !$object->exists()) {
 | |
|                 throw new RuntimeException('Not Found', 404);
 | |
|             }
 | |
| 
 | |
|             // Pages are a special case.
 | |
|             $parent = $object instanceof PageInterface ? $object->parent() : $object;
 | |
|             if (null === $parent || ($parent instanceof FlexAuthorizeInterface && !$parent->isAuthorized('create', 'admin', $this->user))) {
 | |
|                 throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' copy.',
 | |
|                     403);
 | |
|             }
 | |
| 
 | |
|             if ($object instanceof PageInterface && is_array($this->data)) {
 | |
|                 $data = $this->data;
 | |
|                 $blueprints = $this->admin->blueprints('admin/pages/move');
 | |
|                 $blueprints->validate($data);
 | |
|                 $data = $blueprints->filter($data, true, true);
 | |
|                 // Hack for pages
 | |
|                 $data['name'] = $data['name'] ?? $object->template();
 | |
|                 $data['ordering'] = (int)$object->order() > 0;
 | |
|                 $data['order'] = null;
 | |
|                 if (isset($data['title'])) {
 | |
|                     $data['header']['title'] = $data['title'];
 | |
|                     unset($data['title']);
 | |
|                 }
 | |
| 
 | |
|                 $object->order(false);
 | |
|                 $object->update($data);
 | |
|             }
 | |
| 
 | |
|             $object = $object->createCopy();
 | |
| 
 | |
|             $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_COPY_SUCCESS'));
 | |
| 
 | |
|             $this->setRedirect($this->getFlex()->adminRoute($object));
 | |
| 
 | |
|         } catch (RuntimeException $e) {
 | |
|             $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_COPY_FAILURE', $e->getMessage()), 'error');
 | |
|             $this->setRedirect($this->referrerRoute->toString(true), 302);
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * $data['route'] = $this->grav['uri']->param('route');
 | |
|      * $data['sortby'] = $this->grav['uri']->param('sortby', null);
 | |
|      * $data['filters'] = $this->grav['uri']->param('filters', null);
 | |
|      * $data['page'] $this->grav['uri']->param('page', true);
 | |
|      * $data['base'] = $this->grav['uri']->param('base');
 | |
|      * $initial = (bool) $this->grav['uri']->param('initial');
 | |
|      *
 | |
|      * @return ResponseInterface
 | |
|      * @throws RequestException
 | |
|      * @TODO: Move pages specific logic
 | |
|      */
 | |
|     protected function actionGetLevelListing(): ResponseInterface
 | |
|     {
 | |
|         /** @var PageInterface|FlexObjectInterface $object */
 | |
|         $object = $this->getObject($this->id ?? '');
 | |
| 
 | |
|         if (!$object || !method_exists($object, 'getLevelListing')) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $request = $this->getRequest();
 | |
|         $data = $request->getParsedBody();
 | |
| 
 | |
|         if (!isset($data['field'])) {
 | |
|             throw new RequestException($request, 'Bad Request', 400);
 | |
|         }
 | |
| 
 | |
|         // Base64 decode the route
 | |
|         $data['route'] = isset($data['route']) ? base64_decode($data['route']) : null;
 | |
|         $data['filters'] = json_decode($options['filters'] ?? '{}', true, 512, JSON_THROW_ON_ERROR) + ['type' => ['root', 'dir']];
 | |
| 
 | |
|         $initial = $data['initial'] ?? null;
 | |
|         if ($initial) {
 | |
|             $data['leaf_route'] = $data['route'];
 | |
|             $data['route'] = null;
 | |
|             $data['level'] = 1;
 | |
|         }
 | |
| 
 | |
|         [$status, $message, $response,$route] = $object->getLevelListing($data);
 | |
| 
 | |
|         $json = [
 | |
|             'status'  => $status,
 | |
|             'message' => $this->admin::translate($message ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED'),
 | |
|             'route' => $route,
 | |
|             'initial' => (bool)$initial,
 | |
|             'data' => array_values($response)
 | |
|         ];
 | |
| 
 | |
|         return $this->createJsonResponse($json, 200);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * $data['route'] = $this->grav['uri']->param('route');
 | |
|      * $data['sortby'] = $this->grav['uri']->param('sortby', null);
 | |
|      * $data['filters'] = $this->grav['uri']->param('filters', null);
 | |
|      * $data['page'] $this->grav['uri']->param('page', true);
 | |
|      * $data['base'] = $this->grav['uri']->param('base');
 | |
|      * $initial = (bool) $this->grav['uri']->param('initial');
 | |
|      *
 | |
|      * @return ResponseInterface
 | |
|      * @throws RequestException
 | |
|      * @TODO: Move pages specific logic
 | |
|      */
 | |
|     protected function actionListLevel(): ResponseInterface
 | |
|     {
 | |
|         try {
 | |
|             /** @var PageInterface|FlexObjectInterface $object */
 | |
|             $object = $this->getObject('');
 | |
| 
 | |
|             if (!$object || !method_exists($object, 'getLevelListing')) {
 | |
|                 throw new RuntimeException('Not Found', 404);
 | |
|             }
 | |
| 
 | |
|             $directory = $object->getFlexDirectory();
 | |
|             if (!$directory->isAuthorized('list', 'admin', $this->user)) {
 | |
|                 throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' getLevelListing.',
 | |
|                     403);
 | |
|             }
 | |
| 
 | |
|             $request = $this->getRequest();
 | |
|             $data = $request->getParsedBody();
 | |
| 
 | |
|             // Base64 decode the route
 | |
|             $data['route'] = isset($data['route']) ? base64_decode($data['route']) : null;
 | |
|             $data['filters'] = ($data['filters'] ?? []) + ['type' => ['dir']];
 | |
|             $data['lang'] = $this->getLanguage();
 | |
| 
 | |
|             // Display root if permitted.
 | |
|             $action = $directory->getConfig('admin.views.configure.authorize') ?? $directory->getConfig('admin.configure.authorize') ?? 'admin.super';
 | |
|             if ($this->user->authorize($action)) {
 | |
|                 $data['filters']['type'][] = 'root';
 | |
|             }
 | |
| 
 | |
|             $initial = $data['initial'] ?? null;
 | |
|             if ($initial) {
 | |
|                 $data['leaf_route'] = $data['route'];
 | |
|                 $data['route'] = null;
 | |
|                 $data['level'] = 1;
 | |
|             }
 | |
| 
 | |
|             [$status, $message, $response, $route] = $object->getLevelListing($data);
 | |
| 
 | |
|             $json = [
 | |
|                 'status'  => $status,
 | |
|                 'message' => $this->admin::translate($message ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED'),
 | |
|                 'route' => $route,
 | |
|                 'initial' => (bool)$initial,
 | |
|                 'data' => array_values($response)
 | |
|             ];
 | |
|         } catch (Exception $e) {
 | |
|             return $this->createErrorResponse($e);
 | |
|         }
 | |
| 
 | |
|         return $this->createJsonResponse($json, 200);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     public function taskReset(): ResponseInterface
 | |
|     {
 | |
|         $key = $this->id;
 | |
| 
 | |
|         $object = $this->getObject($key);
 | |
|         if (!$object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         /** @var FlexForm $form */
 | |
|         $form = $this->getForm($object);
 | |
|         $form->getFlash()->delete();
 | |
| 
 | |
|         return $this->createRedirectResponse($this->referrerRoute->toString(true));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskSaveas(): bool
 | |
|     {
 | |
|         return $this->taskSave();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskSave(): bool
 | |
|     {
 | |
|         $key = $this->id;
 | |
| 
 | |
|         try {
 | |
|             $object = $this->getObject($key);
 | |
|             if (!$object) {
 | |
|                 throw new RuntimeException('Not Found', 404);
 | |
|             }
 | |
| 
 | |
|             if ($object->exists()) {
 | |
|                 if (!$object->isAuthorized('update', 'admin', $this->user)) {
 | |
|                     throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.',
 | |
|                         403);
 | |
|                 }
 | |
|             } else {
 | |
|                 if (!$object->isAuthorized('create', 'admin', $this->user)) {
 | |
|                     throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.',
 | |
|                         403);
 | |
|                 }
 | |
|             }
 | |
|             $grav = Grav::instance();
 | |
| 
 | |
|             /** @var ServerRequestInterface $request */
 | |
|             $request = $grav['request'];
 | |
| 
 | |
|             /** @var FlexForm $form */
 | |
|             $form = $this->getForm($object);
 | |
| 
 | |
|             $callable = function (array $data, array $files, FlexObject $object) use ($form) {
 | |
|                 if (method_exists($object, 'storeOriginal')) {
 | |
|                     $object->storeOriginal();
 | |
|                 }
 | |
|                 $object->update($data, $files);
 | |
| 
 | |
|                 // Support for expert mode.
 | |
|                 if (str_ends_with($form->getId(), '-raw') && isset($data['frontmatter']) && is_callable([$object, 'frontmatter'])) {
 | |
|                     if (!$this->user->authorize('admin.super')) {
 | |
|                         throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save raw.',
 | |
|                         403);
 | |
|                     }
 | |
|                     $object->frontmatter($data['frontmatter']);
 | |
|                     unset($data['frontmatter']);
 | |
|                 }
 | |
| 
 | |
|                 $object->save();
 | |
|             };
 | |
| 
 | |
|             $form->setSubmitMethod($callable);
 | |
|             $form->handleRequest($request);
 | |
|             $error = $form->getError();
 | |
|             $errors = $form->getErrors();
 | |
|             if ($errors) {
 | |
|                 if ($error) {
 | |
|                     $this->admin->setMessage($error, 'error');
 | |
|                 }
 | |
| 
 | |
|                 foreach ($errors as $field => $list) {
 | |
|                     foreach ((array)$list as $message) {
 | |
|                         $this->admin->setMessage($message, 'error');
 | |
|                     }
 | |
|                 }
 | |
|                 throw new RuntimeException('Form validation failed, please check your input');
 | |
|             }
 | |
|             if ($error) {
 | |
|                 throw new RuntimeException($error);
 | |
|             }
 | |
| 
 | |
|             $object = $form->getObject();
 | |
|             $objectKey = $object->getKey();
 | |
| 
 | |
|             $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_SAVE_SUCCESS'));
 | |
| 
 | |
|             // Set route to point to the current page.
 | |
|             if (!$this->redirect) {
 | |
|                 $postAction = $request->getParsedBody()['_post_entries_save'] ?? 'edit';
 | |
|                 if ($postAction === 'create-new') {
 | |
|                     // Create another.
 | |
|                     $route = $this->referrerRoute->withGravParam('action', null)->withGravParam('', 'add');
 | |
|                 } elseif ($postAction === 'list') {
 | |
|                     // Back to listing.
 | |
|                     $route = $this->currentRoute;
 | |
| 
 | |
|                     // Remove :add action.
 | |
|                     $actionAdd = $key === '' || $route->getGravParam('action') === 'add' || $route->getGravParam('') === 'add';
 | |
|                     if ($actionAdd) {
 | |
|                         $route = $route->withGravParam('action', null)->withGravParam('', null);
 | |
|                     }
 | |
| 
 | |
|                     $len = ($key === '' ? 0 : -1) - \substr_count($key, '/');
 | |
|                     if ($len) {
 | |
|                         $route = $route->withRoute($route->getRoute(0, $len));
 | |
|                     }
 | |
|                 } else {
 | |
|                     // Back to edit.
 | |
|                     $route = $this->currentRoute;
 | |
|                     $isRoot = $object instanceof PageInterface && $object->root();
 | |
|                     $hasKeyChanged = !$isRoot && $key !== $objectKey;
 | |
| 
 | |
|                     // Remove :add action.
 | |
|                     $actionAdd = $key === '' || $route->getGravParam('action') === 'add' || $route->getGravParam('') === 'add';
 | |
|                     if ($actionAdd) {
 | |
|                         $route = $route->withGravParam('action', null)->withGravParam('', null);
 | |
|                     }
 | |
| 
 | |
|                     if ($hasKeyChanged) {
 | |
|                         if ($key === '') {
 | |
|                             // Append new key.
 | |
|                             $path = $route->getRoute() . '/' . $objectKey;
 | |
|                         } elseif ($objectKey === '') {
 | |
|                             // Remove old key.
 | |
|                             $path = preg_replace('|/' . preg_quote($key, '|') . '$|u', '/', $route->getRoute());
 | |
|                         } else {
 | |
|                             // Replace old key with new key.
 | |
|                             $path = preg_replace('|/' . preg_quote($key, '|') . '$|u', '/' . $objectKey, $route->getRoute());
 | |
|                         }
 | |
|                         $route = $route->withRoute($path);
 | |
|                     }
 | |
| 
 | |
|                     // Make sure we're using the correct language.
 | |
|                     $lang = null;
 | |
|                     if ($object instanceof FlexTranslateInterface) {
 | |
|                         $lang = $object->getLanguage();
 | |
|                         $route = $route->withLanguage($lang);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 $this->setRedirect($route->toString(true));
 | |
|             }
 | |
| 
 | |
|             $grav = Grav::instance();
 | |
|             $grav->fireEvent('onFlexAfterSave', new Event(['type' => 'flex', 'object' => $object]));
 | |
|         } catch (RuntimeException $e) {
 | |
|             $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_SAVE_FAILURE', $e->getMessage()), 'error');
 | |
| 
 | |
|             if (isset($object, $form)) {
 | |
|                 $data = $form->getData();
 | |
|                 if (null !== $data) {
 | |
|                     $flash = $form->getFlash();
 | |
|                     $flash->setObject($object);
 | |
|                     $flash->setData($data->toArray());
 | |
|                     $flash->save();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // $this->setRedirect($this->referrerRoute->withQueryParam('uid', $flash->getUniqueId())->toString(true), 302);
 | |
|             $this->setRedirect($this->referrerRoute->toString(true), 302);
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskConfigure(): bool
 | |
|     {
 | |
|         try {
 | |
|             $directory = $this->getDirectory();
 | |
|             $config = $directory->getConfig('admin.views.configure.authorize') ?? $directory->getConfig('admin.configure.authorize') ?? 'admin.super';
 | |
|             if (!$this->user->authorize($config)) {
 | |
|                 throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' configure.', 403);
 | |
|             }
 | |
| 
 | |
|             $grav = Grav::instance();
 | |
| 
 | |
|             /** @var ServerRequestInterface $request */
 | |
|             $request = $grav['request'];
 | |
| 
 | |
|             /** @var FlexForm $form */
 | |
|             $form = $this->getDirectoryForm();
 | |
|             $form->handleRequest($request);
 | |
|             $error = $form->getError();
 | |
|             $errors = $form->getErrors();
 | |
|             if ($errors) {
 | |
|                 if ($error) {
 | |
|                     $this->admin->setMessage($error, 'error');
 | |
|                 }
 | |
| 
 | |
|                 foreach ($errors as $field => $list) {
 | |
|                     foreach ((array)$list as $message) {
 | |
|                         $this->admin->setMessage($message, 'error');
 | |
|                     }
 | |
|                 }
 | |
|                 throw new RuntimeException('Form validation failed, please check your input');
 | |
|             }
 | |
|             if ($error) {
 | |
|                 throw new RuntimeException($error);
 | |
|             }
 | |
| 
 | |
|             $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_CONFIGURE_SUCCESS'));
 | |
| 
 | |
|             if (!$this->redirect) {
 | |
|                 $this->referrerRoute = $this->currentRoute;
 | |
| 
 | |
|                 $this->setRedirect($this->referrerRoute->toString(true));
 | |
|             }
 | |
|         } catch (RuntimeException $e) {
 | |
|             $this->admin->setMessage($this->admin::translate('PLUGIN_FLEX_OBJECTS.CONTROLLER.TASK_CONFIGURE_FAILURE', $e->getMessage()), 'error');
 | |
|             $this->setRedirect($this->referrerRoute->toString(true), 302);
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskMediaList(): bool
 | |
|     {
 | |
|         try {
 | |
|             $response = $this->forwardMediaTask('action', 'media.list');
 | |
| 
 | |
|             $this->admin->json_response = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
 | |
|         } catch (Exception $e) {
 | |
|             die($e->getMessage());
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskMediaUpload(): bool
 | |
|     {
 | |
|         try {
 | |
|             $response = $this->forwardMediaTask('task', 'media.upload');
 | |
| 
 | |
|             $this->admin->json_response = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
 | |
|         } catch (Exception $e) {
 | |
|             die($e->getMessage());
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskMediaDelete(): bool
 | |
|     {
 | |
|         try {
 | |
|             $response = $this->forwardMediaTask('task', 'media.delete');
 | |
| 
 | |
|             $this->admin->json_response = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
 | |
|         } catch (Exception $e) {
 | |
|             die($e->getMessage());
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskListmedia(): bool
 | |
|     {
 | |
|         return $this->taskMediaList();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskAddmedia(): bool
 | |
|     {
 | |
|         try {
 | |
|             $response = $this->forwardMediaTask('task', 'media.copy');
 | |
| 
 | |
|             $this->admin->json_response = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
 | |
|         } catch (Exception $e) {
 | |
|             die($e->getMessage());
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskDelmedia(): bool
 | |
|     {
 | |
|         try {
 | |
|             $response = $this->forwardMediaTask('task', 'media.remove');
 | |
| 
 | |
|             $this->admin->json_response = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
 | |
|         } catch (Exception $e) {
 | |
|             die($e->getMessage());
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      * @deprecated Do not use
 | |
|      */
 | |
|     public function taskFilesUpload(): bool
 | |
|     {
 | |
|         throw new RuntimeException('Task filesUpload should not be called!');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string|null $filename
 | |
|      * @return bool
 | |
|      * @deprecated Do not use
 | |
|      */
 | |
|     public function taskRemoveMedia($filename = null): bool
 | |
|     {
 | |
|         throw new RuntimeException('Task removeMedia should not be called!');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function taskGetFilesInFolder(): bool
 | |
|     {
 | |
|         try {
 | |
|             $response = $this->forwardMediaTask('action', 'media.picker');
 | |
| 
 | |
|             $this->admin->json_response = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
 | |
|         } catch (Exception $e) {
 | |
|             $this->admin->json_response = ['success' => false, 'error' => $e->getMessage()];
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $type
 | |
|      * @param string $name
 | |
|      * @return ResponseInterface
 | |
|      */
 | |
|     protected function forwardMediaTask(string $type, string $name): ResponseInterface
 | |
|     {
 | |
|         $route = Uri::getCurrentRoute()->withGravParam('task', null)->withGravParam($type, $name);
 | |
|         $object = $this->getObject();
 | |
| 
 | |
|         /** @var ServerRequest $request */
 | |
|         $request = $this->grav['request'];
 | |
|         $request = $request
 | |
|             ->withAttribute('type', $this->target)
 | |
|             ->withAttribute('key', $this->id)
 | |
|             ->withAttribute('storage_key', $object && $object->exists() ? $object->getStorageKey() : null)
 | |
|             ->withAttribute('route', $route)
 | |
|             ->withAttribute('forwarded', true)
 | |
|             ->withAttribute('object', $object);
 | |
| 
 | |
|         $controller = new MediaController();
 | |
|         $controller->setUser($this->user);
 | |
| 
 | |
|         return $controller->handle($request);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return Flex
 | |
|      */
 | |
|     protected function getFlex(): Flex
 | |
|     {
 | |
|         return Grav::instance()['flex_objects'];
 | |
|     }
 | |
| 
 | |
|     public static function getInstance(): ?AdminController
 | |
|     {
 | |
|         return self::$instance;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * AdminController constructor.
 | |
|      */
 | |
|     public function __construct()
 | |
|     {
 | |
|         self::$instance = $this;
 | |
| 
 | |
|         $this->grav = Grav::instance();
 | |
|         $this->admin = Grav::instance()['admin'];
 | |
|         $this->user = $this->admin->user;
 | |
| 
 | |
|         $this->active = false;
 | |
| 
 | |
|         // Controller can only be run in admin.
 | |
|         if (!Utils::isAdminPlugin()) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         [, $location, $target] = $this->grav['admin']->getRouteDetails();
 | |
|         if (!$location) {
 | |
|             return;
 | |
|         }
 | |
|         $target = \is_string($target) ? urldecode($target) : null;
 | |
|         $id = null;
 | |
| 
 | |
|         /** @var Uri $uri */
 | |
|         $uri = $this->grav['uri'];
 | |
|         $routeObject = $uri::getCurrentRoute();
 | |
|         $routeObject->withExtension('');
 | |
| 
 | |
|         $routes = $this->getAdminRoutes();
 | |
| 
 | |
|         // Match route to the flex directory.
 | |
|         $path = '/' . ($target ? $location . '/' . $target : $location) . '/';
 | |
|         $test = $routes[$path] ?? null;
 | |
| 
 | |
|         $directory = null;
 | |
|         if ($test)  {
 | |
|             $directory = $test['directory'];
 | |
|             $location = trim($path, '/');
 | |
|             $target = '';
 | |
|         } else {
 | |
|             krsort($routes);
 | |
|             foreach ($routes as $route => $test) {
 | |
|                 if (strpos($path, $route) === 0) {
 | |
|                     $directory = $test['directory'];
 | |
|                     $location = trim($route, '/');
 | |
|                     $target = trim(substr($path, strlen($route)), '/');
 | |
|                     break;
 | |
|                 }
 | |
|                 $test = null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if ($directory) {
 | |
|             // Redirect aliases.
 | |
|             if (isset($test['redirect'])) {
 | |
|                 $route = $test['redirect'];
 | |
|                 // If directory route starts with alias and path continues, stop.
 | |
|                 if ($target && strpos($route, $location) === 0) {
 | |
|                     // We are not in a directory.
 | |
|                     return;
 | |
|                 }
 | |
|                 $redirect = '/' . $route . ($target ? '/' . $target : '');
 | |
|                 $this->setRedirect($redirect, 302);
 | |
|                 $this->redirect();
 | |
|             } elseif (isset($test['action'])) {
 | |
|                 $routeObject = $routeObject->withGravParam('', $test['action']);
 | |
|             }
 | |
| 
 | |
|             $id = $target;
 | |
|             $target = $directory->getFlexType();
 | |
|         } else {
 | |
|             // We are not in a directory.
 | |
|             if ($location !== 'flex-objects') {
 | |
|                 return;
 | |
|             }
 | |
|             $array = explode('/', $target, 2);
 | |
|             $target = array_shift($array) ?: null;
 | |
|             $id = array_shift($array) ?: null;
 | |
|         }
 | |
| 
 | |
|         // Post
 | |
|         $post = $_POST ?? [];
 | |
|         if (isset($post['data'])) {
 | |
|             $this->data = $this->getPost($post['data']);
 | |
|             unset($post['data']);
 | |
|         }
 | |
| 
 | |
|         // Task
 | |
|         $task = $this->grav['task'];
 | |
|         if ($task) {
 | |
|             $this->task = $task;
 | |
|         }
 | |
| 
 | |
|         $this->post = $this->getPost($post);
 | |
|         $this->location = 'flex-objects';
 | |
|         $this->target = $target;
 | |
|         $this->id = $this->post['id'] ?? $id;
 | |
|         $this->action = $this->post['action'] ?? $uri->param('action', null) ?? $uri->param('', null) ?? $routeObject->getGravParam('');
 | |
|         $this->active = true;
 | |
|         $this->currentRoute = $uri::getCurrentRoute();
 | |
|         $this->route = $routeObject;
 | |
| 
 | |
|         $base = $this->grav['pages']->base();
 | |
|         if ($base) {
 | |
|             // Fix referrer for sub-folder multi-site setups.
 | |
|             $referrer = preg_replace('`^' . $base . '`', '', $uri->referrer());
 | |
|         } else {
 | |
|             $referrer = $uri->referrer();
 | |
|         }
 | |
|         $this->referrerRoute = $referrer ? RouteFactory::createFromString($referrer) : $this->currentRoute;
 | |
|     }
 | |
| 
 | |
|     public function getInfo(): array
 | |
|     {
 | |
|         if (!$this->isActive()) {
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         $class = AdminController::class;
 | |
|         return [
 | |
|             'controller' => [
 | |
|                 'name' => $this->location,
 | |
|                 'instance' => [$class, 'getInstance']
 | |
|             ],
 | |
|             'location' => $this->location,
 | |
|             'type' => $this->target,
 | |
|             'key' => $this->id,
 | |
|             'action' => $this->action,
 | |
|             'task' => $this->task
 | |
|         ];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Performs a task or action on a post or target.
 | |
|      *
 | |
|      * @return ResponseInterface|bool|null
 | |
|      */
 | |
|     public function execute()
 | |
|     {
 | |
|         if (!$this->user->authorize('admin.login')) {
 | |
|             // TODO: improve
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         $params = [];
 | |
| 
 | |
|         $event = new Event(
 | |
|             [
 | |
|                 'type' => &$this->target,
 | |
|                 'key' => &$this->id,
 | |
|                 'directory' => &$this->directory,
 | |
|                 'collection' => &$this->collection,
 | |
|                 'object' => &$this->object
 | |
|             ]
 | |
|         );
 | |
|         $this->grav->fireEvent("flex.{$this->target}.admin.route", $event);
 | |
| 
 | |
|         if ($this->isFormSubmit()) {
 | |
|             $form = $this->getForm();
 | |
|             $this->nonce_name = $form->getNonceName();
 | |
|             $this->nonce_action = $form->getNonceAction();
 | |
|         }
 | |
| 
 | |
|         // Handle Task & Action
 | |
|         if ($this->task) {
 | |
|             // validate nonce
 | |
|             if (!$this->validateNonce()) {
 | |
|                 $e = new RequestException($this->getRequest(), 'Page Expired', 400);
 | |
| 
 | |
|                 $this->close($this->createErrorResponse($e));
 | |
|             }
 | |
|             $method = $this->task_prefix . ucfirst(str_replace('.', '', $this->task));
 | |
| 
 | |
|             if (!method_exists($this, $method)) {
 | |
|                 $method = $this->task_prefix . 'Default';
 | |
|             }
 | |
| 
 | |
|         } elseif ($this->target) {
 | |
|             if (!$this->action) {
 | |
|                 if ($this->id) {
 | |
|                     $this->action = 'edit';
 | |
|                     $params[] = $this->id;
 | |
|                 } else {
 | |
|                     $this->action = 'list';
 | |
|                 }
 | |
|             }
 | |
|             $method = 'action' . ucfirst(strtolower(str_replace('.', '', $this->action)));
 | |
| 
 | |
|             if (!method_exists($this, $method)) {
 | |
|                 $method = $this->action_prefix . 'Default';
 | |
|             }
 | |
|         } else {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         if (!method_exists($this, $method)) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $response = $this->{$method}(...$params);
 | |
|         } catch (RequestException $e) {
 | |
|             $response = $this->createErrorResponse($e);
 | |
|         } catch (RuntimeException $e) {
 | |
|             // If task fails to run, redirect back to the previous page and display the error message.
 | |
|             if ($this->task && !$this->redirect) {
 | |
|                 $this->setRedirect($this->referrerRoute->toString(true));
 | |
|             }
 | |
|             $response = null;
 | |
|             $this->setMessage($e->getMessage(), 'error');
 | |
|         }
 | |
| 
 | |
|         if ($response instanceof ResponseInterface) {
 | |
|             $this->close($response);
 | |
|         }
 | |
| 
 | |
|         // Grab redirect parameter.
 | |
|         $redirect = $this->post['_redirect'] ?? null;
 | |
|         unset($this->post['_redirect']);
 | |
| 
 | |
|         // Redirect if requested.
 | |
|         if ($redirect) {
 | |
|             $this->setRedirect($redirect);
 | |
|         }
 | |
| 
 | |
|         return $response;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function isFormSubmit(): bool
 | |
|     {
 | |
|         return (bool)($this->post['__form-name__'] ?? null);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param FlexObjectInterface|null $object
 | |
|      * @return FlexFormInterface
 | |
|      */
 | |
|     public function getForm(FlexObjectInterface $object = null): FlexFormInterface
 | |
|     {
 | |
|         $object = $object ?? $this->getObject();
 | |
|         if (!$object) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $formName = $this->post['__form-name__'] ?? '';
 | |
|         $name = '';
 | |
|         $uniqueId = null;
 | |
| 
 | |
|         // Get the form name. This defines the blueprint which is being used.
 | |
|         if (strpos($formName, '-')) {
 | |
|             $parts = explode('-', $formName);
 | |
|             $prefix = $parts[0] ?? '';
 | |
|             $type = $parts[1] ?? '';
 | |
|             if ($prefix === 'flex' && $type === $object->getFlexType()) {
 | |
|                 $name = $parts[2] ?? '';
 | |
|                 if ($name === 'object') {
 | |
|                     $name = '';
 | |
|                 }
 | |
|                 $uniqueId = $this->post['__unique_form_id__'] ?? null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $options = [
 | |
|             'unique_id' => $uniqueId,
 | |
|         ];
 | |
| 
 | |
|         return $object->getForm($name, $options);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param FlexDirectoryInterface|null $directory
 | |
|      * @return FlexFormInterface
 | |
|      */
 | |
|     public function getDirectoryForm(FlexDirectoryInterface $directory = null): FlexFormInterface
 | |
|     {
 | |
|         $directory = $directory ?? $this->getDirectory();
 | |
|         if (!$directory) {
 | |
|             throw new RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $formName = $this->post['__form-name__'] ?? '';
 | |
|         $name = '';
 | |
|         $uniqueId = null;
 | |
| 
 | |
|         // Get the form name. This defines the blueprint which is being used.
 | |
|         if (strpos($formName, '-')) {
 | |
|             $parts = explode('-', $formName);
 | |
|             $prefix = $parts[0] ?? '';
 | |
|             $type = $parts[1] ?? '';
 | |
|             if ($prefix === 'flex_conf' && $type === $directory->getFlexType()) {
 | |
|                 $name = $parts[2] ?? '';
 | |
|                 $uniqueId = $this->post['__unique_form_id__'] ?? null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $options = [
 | |
|             'unique_id' => $uniqueId,
 | |
|         ];
 | |
| 
 | |
|         return $directory->getDirectoryForm($name, $options);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string|null $key
 | |
|      * @return FlexObjectInterface|null
 | |
|      */
 | |
|     public function getObject(string $key = null): ?FlexObjectInterface
 | |
|     {
 | |
|         if (null === $this->object) {
 | |
|             $key = $key ?? $this->id;
 | |
|             $object = false;
 | |
| 
 | |
|             $directory = $this->getDirectory();
 | |
|             if ($directory) {
 | |
|                 // FIXME: hack for pages.
 | |
|                 if ($key === '_root') {
 | |
|                     $index = $directory->getIndex();
 | |
|                     if ($index instanceof PageIndex) {
 | |
|                         $object = $index->getRoot();
 | |
|                     }
 | |
|                 } elseif (null !== $key) {
 | |
|                     $object = $directory->getObject($key) ?? $directory->createObject([], $key);
 | |
|                 } elseif ($this->action === 'add') {
 | |
|                     $object = $directory->createObject([], '');
 | |
|                 }
 | |
| 
 | |
|                 if ($object instanceof FlexTranslateInterface && $this->isMultilang()) {
 | |
|                     $language = $this->getLanguage();
 | |
|                     if ($object->hasTranslation($language)) {
 | |
|                         $object = $object->getTranslation($language);
 | |
|                     } elseif (!in_array('', $object->getLanguages(true), true)) {
 | |
|                         $object->language($language);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (is_callable([$object, 'refresh'])) {
 | |
|                     $object->refresh();
 | |
|                 }
 | |
| 
 | |
|                 // Get updated object via form.
 | |
|                 $this->object = $object ? $object->getForm()->getObject() : false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $this->object ?: null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string|null $type
 | |
|      * @return FlexDirectoryInterface|null
 | |
|      */
 | |
|     public function getDirectory(string $type = null): ?FlexDirectoryInterface
 | |
|     {
 | |
|         $type = $type ?? $this->target;
 | |
| 
 | |
|         if ($type && null === $this->directory) {
 | |
|             $this->directory = Grav::instance()['flex_objects']->getDirectory($type);
 | |
|         }
 | |
| 
 | |
|         return $this->directory;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return FlexCollectionInterface|null
 | |
|      */
 | |
|     public function getCollection(): ?FlexCollectionInterface
 | |
|     {
 | |
|         if (null === $this->collection) {
 | |
|             $directory = $this->getDirectory();
 | |
| 
 | |
|             $this->collection = $directory ? $directory->getCollection() : null;
 | |
|         }
 | |
| 
 | |
|         return $this->collection;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $msg
 | |
|      * @param string $type
 | |
|      * @return void
 | |
|      */
 | |
|     public function setMessage(string $msg, string $type = 'info'): void
 | |
|     {
 | |
|         /** @var Message $messages */
 | |
|         $messages = $this->grav['messages'];
 | |
|         $messages->add($msg, $type);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return bool
 | |
|      */
 | |
|     public function isActive(): bool
 | |
|     {
 | |
|         return (bool) $this->active;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $location
 | |
|      * @return void
 | |
|      */
 | |
|     public function setLocation(string $location): void
 | |
|     {
 | |
|         $this->location = $location;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return string|null
 | |
|      */
 | |
|     public function getLocation(): ?string
 | |
|     {
 | |
|         return $this->location;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $action
 | |
|      * @return void
 | |
|      */
 | |
|     public function setAction(string $action): void
 | |
|     {
 | |
|         $this->action = $action;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return string|null
 | |
|      */
 | |
|     public function getAction(): ?string
 | |
|     {
 | |
|         return $this->action;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $task
 | |
|      * @return void
 | |
|      */
 | |
|     public function setTask(string $task): void
 | |
|     {
 | |
|         $this->task = $task;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return string|null
 | |
|      */
 | |
|     public function getTask(): ?string
 | |
|     {
 | |
|         return $this->task;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $target
 | |
|      * @return void
 | |
|      */
 | |
|     public function setTarget(string $target): void
 | |
|     {
 | |
|         $this->target = $target;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return string|null
 | |
|      */
 | |
|     public function getTarget(): ?string
 | |
|     {
 | |
|         return $this->target;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $id
 | |
|      * @return void
 | |
|      */
 | |
|     public function setId(string $id): void
 | |
|     {
 | |
|         $this->id = $id;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return string|null
 | |
|      */
 | |
|     public function getId(): ?string
 | |
|     {
 | |
|         return $this->id;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the page redirect.
 | |
|      *
 | |
|      * @param string $path The path to redirect to
 | |
|      * @param int    $code The HTTP redirect code
 | |
|      * @return void
 | |
|      */
 | |
|     public function setRedirect(string $path, int $code = 303): void
 | |
|     {
 | |
|         $this->redirect     = $path;
 | |
|         $this->redirectCode = (int)$code;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Redirect to the route stored in $this->redirect
 | |
|      *
 | |
|      * @return void
 | |
|      */
 | |
|     public function redirect(): void
 | |
|     {
 | |
|         $this->admin->redirect($this->redirect, $this->redirectCode);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return array
 | |
|      */
 | |
|     public function getAdminRoutes(): array
 | |
|     {
 | |
|         if (null === $this->adminRoutes) {
 | |
|             $routes = [];
 | |
|             /** @var FlexDirectoryInterface $directory */
 | |
|             foreach ($this->getFlex()->getDirectories() as $directory) {
 | |
|                 $config = $directory->getConfig('admin');
 | |
|                 if (!$directory->isEnabled() || !empty($config['disabled'])) {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 // Alias under flex-objects (always exists, but can be redirected).
 | |
|                 $routes["/flex-objects/{$directory->getFlexType()}/"] = ['directory' => $directory];
 | |
| 
 | |
|                 $route = $config['router']['path'] ?? $config['menu']['list']['route'] ?? null;
 | |
|                 if ($route) {
 | |
|                     $routes[$route . '/'] = ['directory' => $directory];
 | |
|                 }
 | |
| 
 | |
|                 $redirects = (array)($config['router']['redirects'] ?? null);
 | |
|                 foreach ($redirects as $redirectFrom => $redirectTo) {
 | |
|                     $redirectFrom .= '/';
 | |
|                     if (!isset($routes[$redirectFrom])) {
 | |
|                         $routes[$redirectFrom] = ['directory' => $directory, 'redirect' => $redirectTo];
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 $actions = (array)($config['router']['actions'] ?? null);
 | |
|                 foreach ($actions as $name => $action) {
 | |
|                     if (is_array($action)) {
 | |
|                         $path = $action['path'] ?? null;
 | |
|                     } else {
 | |
|                         $path = $action;
 | |
|                     }
 | |
|                     if ($path !== null) {
 | |
|                         $routes[$path . '/'] = ['directory' => $directory, 'action' => $name];
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             $this->adminRoutes = $routes;
 | |
|         }
 | |
| 
 | |
|         return $this->adminRoutes;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return true if multilang is active
 | |
|      *
 | |
|      * @return bool True if multilang is active
 | |
|      */
 | |
|     protected function isMultilang(): bool
 | |
|     {
 | |
|         /** @var Language $language */
 | |
|         $language = $this->grav['language'];
 | |
| 
 | |
|         return $language->enabled();
 | |
|     }
 | |
| 
 | |
|     protected function validateNonce(): bool
 | |
|     {
 | |
|         $nonce_action = $this->nonce_action;
 | |
|         $nonce = $this->post[$this->nonce_name] ?? $this->grav['uri']->param($this->nonce_name) ?? $this->grav['uri']->query($this->nonce_name);
 | |
| 
 | |
|         if (!$nonce) {
 | |
|             $nonce = $this->post['admin-nonce'] ?? $this->grav['uri']->param('admin-nonce') ?? $this->grav['uri']->query('admin-nonce');
 | |
|             $nonce_action = 'admin-form';
 | |
|         }
 | |
| 
 | |
|         return $nonce && Utils::verifyNonce($nonce, $nonce_action);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Prepare and return POST data.
 | |
|      *
 | |
|      * @param array $post
 | |
|      * @return array
 | |
|      */
 | |
|     protected function getPost(array $post): array
 | |
|     {
 | |
|         unset($post['task']);
 | |
| 
 | |
|         // Decode JSON encoded fields and merge them to data.
 | |
|         if (isset($post['_json'])) {
 | |
|             $post = array_replace_recursive($post, $this->jsonDecode($post['_json']));
 | |
|             unset($post['_json']);
 | |
|         }
 | |
| 
 | |
|         $post = $this->cleanDataKeys($post);
 | |
| 
 | |
|         return $post;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param ResponseInterface $response
 | |
|      * @return never-return
 | |
|      */
 | |
|     protected function close(ResponseInterface $response): void
 | |
|     {
 | |
|         $this->grav->close($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Recursively JSON decode data.
 | |
|      *
 | |
|      * @param  array $data
 | |
|      * @return array
 | |
|      */
 | |
|     protected function jsonDecode(array $data)
 | |
|     {
 | |
|         foreach ($data as &$value) {
 | |
|             if (is_array($value)) {
 | |
|                 $value = $this->jsonDecode($value);
 | |
|             } else {
 | |
|                 $value = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $data;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param array $source
 | |
|      * @return array
 | |
|      */
 | |
|     protected function cleanDataKeys($source = []): array
 | |
|     {
 | |
|         $out = [];
 | |
| 
 | |
|         if (is_array($source)) {
 | |
|             foreach ($source as $key => $value) {
 | |
|                 $key = str_replace(['%5B', '%5D'], ['[', ']'], $key);
 | |
|                 if (is_array($value)) {
 | |
|                     $out[$key] = $this->cleanDataKeys($value);
 | |
|                 } else {
 | |
|                     $out[$key] = $value;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $out;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return string
 | |
|      */
 | |
|     public function getLanguage(): string
 | |
|     {
 | |
|         return $this->admin->language ?? '';
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return Config
 | |
|      */
 | |
|     protected function getConfig(): Config
 | |
|     {
 | |
|         return $this->grav['config'];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return ServerRequestInterface
 | |
|      */
 | |
|     protected function getRequest(): ServerRequestInterface
 | |
|     {
 | |
|         return $this->grav['request'];
 | |
|     }
 | |
| }
 |