| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911 | 
							- <?php
 
- namespace Grav\Plugin\FlexObjects\Admin;
 
- use Exception;
 
- use Grav\Common\Cache;
 
- use Grav\Common\Config\Config;
 
- use Grav\Common\Data\Data;
 
- 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\Helpers\Excerpts;
 
- 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 void
 
-      */
 
-     public function taskDefault(): void
 
-     {
 
-         $type = $this->target;
 
-         $directory = $this->getDirectory($type);
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         $object = $this->getObject();
 
-         $key = $this->id;
 
-         if ($object && $object->exists()) {
 
-             $event = new Event(
 
-                 [
 
-                     'type' => $type,
 
-                     'key' => $key,
 
-                     'admin' => $this->admin,
 
-                     'flex' => $this->getFlex(),
 
-                     'directory' => $directory,
 
-                     'object' => $object,
 
-                     'data' => $this->data,
 
-                     'user' => $this->user,
 
-                     'redirect' => $this->redirect
 
-                 ]
 
-             );
 
-             try {
 
-                 $this->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);
 
-             }
 
-         }
 
-     }
 
-     /**
 
-      * Default action, onFlexAction[NAME] event.
 
-      *
 
-      * @return void
 
-      */
 
-     public function actionDefault(): void
 
-     {
 
-         $type = $this->target;
 
-         $directory = $this->getDirectory($type);
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         $object = $this->getObject();
 
-         $key = $this->id;
 
-         if ($object && $object->exists()) {
 
-             $event = new Event(
 
-                 [
 
-                     'type' => $type,
 
-                     'key' => $key,
 
-                     'admin' => $this->admin,
 
-                     'flex' => $this->getFlex(),
 
-                     'directory' => $directory,
 
-                     'object' => $object,
 
-                     'user' => $this->user,
 
-                     'redirect' => $this->redirect
 
-                 ]
 
-             );
 
-             try {
 
-                 $this->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);
 
-             }
 
-         }
 
-     }
 
-     /**
 
-      * Get datatable for list view.
 
-      *
 
-      * @return ResponseInterface|null
 
-      */
 
-     public function actionList(): ?ResponseInterface
 
-     {
 
-         $directory = $this->getDirectory();
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         // Check authorization.
 
-         if (!$directory->isAuthorized('list', 'admin', $this->user)) {
 
-             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' list.', 403);
 
-         }
 
-         /** @var Uri $uri */
 
-         $uri = $this->grav['uri'];
 
-         if ($uri->extension() === 'json') {
 
-             $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);
 
-             return $this->createJsonResponse($table->jsonSerialize());
 
-         }
 
-         return null;
 
-     }
 
-     /**
 
-      * Alias for Export action.
 
-      *
 
-      * @return ResponseInterface|null
 
-      */
 
-     public function actionCsv(): ?ResponseInterface
 
-     {
 
-         return $this->actionExport();
 
-     }
 
-     /**
 
-      * Export action. Defaults to CVS export.
 
-      *
 
-      * @return ResponseInterface|null
 
-      */
 
-     public function actionExport(): ?ResponseInterface
 
-     {
 
-         $collection = $this->getCollection();
 
-         if (!$collection) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         // Check authorization.
 
-         $directory = $collection->getFlexDirectory();
 
-         $authorized = is_callable([$collection, 'isAuthorized'])
 
-             ? $collection->isAuthorized('read', 'admin', $this->user)
 
-             : $directory->isAuthorized('read', 'admin', $this->user);
 
-         if (!$authorized) {
 
-             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' read.', 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);
 
-         }
 
-         $queryParams = $this->getRequest()->getQueryParams();
 
-         $type = $queryParams['type'] ?? null;
 
-         if ($type) {
 
-             $config = $config['options'][$type] ?? null;
 
-             if (!$config) {
 
-                 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 = $type ? $collection->{$method}($type) : $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)
 
-         );
 
-         return $response;
 
-     }
 
-     /**
 
-      * Delete object from directory.
 
-      *
 
-      * @return void
 
-      */
 
-     public function taskDelete(): void
 
-     {
 
-         $directory = $this->getDirectory();
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         $object = null;
 
-         try {
 
-             $object = $this->getObject();
 
-             if ($object && $object->exists()) {
 
-                 $authorized = $object instanceof FlexAuthorizeInterface
 
-                     ? $object->isAuthorized('delete', 'admin', $this->user)
 
-                     : $directory->isAuthorized('delete', 'admin', $this->user);
 
-                 if (!$authorized) {
 
-                     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);
 
-                 $this->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);
 
-         }
 
-     }
 
-     /**
 
-      * 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);
 
-         }
 
-         $collection = $directory->getIndex();
 
-         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! However we test against /="' as they are dangerous characters.
 
-         $folder = mb_strtolower($data['folder'] ?? '');
 
-         if ($folder === '' || preg_match('![="\']!u', $folder) !== 0) {
 
-             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);
 
-         }
 
-         if (!$parent->isAuthorized('create', 'admin', $this->user)) {
 
-             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' create.', 403);
 
-         }
 
-         $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');
 
-         $directory->getCache('index')->clear();
 
-         $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);
 
-         }
 
-         if ($directory->getObject() instanceof PageInterface) {
 
-             $this->continuePages($directory);
 
-         } else {
 
-             $this->continue($directory);
 
-         }
 
-     }
 
-     /**
 
-      * @param FlexDirectoryInterface $directory
 
-      * @return void
 
-      */
 
-     protected function continue(FlexDirectoryInterface $directory): void
 
-     {
 
-         $config = $directory->getConfig('admin');
 
-         $supported = !empty($config['modals']['add']);
 
-         if (!$supported) {
 
-             throw new RuntimeException('Task continue is not supported by the type', 400);
 
-         }
 
-         $authorized = $directory->isAuthorized('create', 'admin', $this->user);
 
-         if (!$authorized) {
 
-             $this->setRedirect($this->referrerRoute->toString(true));
 
-             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' add.', 403);
 
-         }
 
-         $this->object = $directory->createObject($this->data, '');
 
-         // 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);
 
-         $this->setRedirect($flash->getUrl());
 
-     }
 
-     /**
 
-      * Create a new page (from modal).
 
-      *
 
-      * TODO: Move pages specific logic
 
-      *
 
-      * @return void
 
-      */
 
-     protected function continuePages(FlexDirectoryInterface $directory): void
 
-     {
 
-         $this->data['route'] = '/' . trim($this->data['route'] ?? '', '/');
 
-         $route = trim($this->data['route'], '/');
 
-         if ($route) {
 
-             $parent = $directory->getObject($route);
 
-         } else {
 
-             // Use root page or fail back to directory auth.
 
-             $index = $directory->getIndex();
 
-             $parent = $index->getRoot() ?? $directory;
 
-         }
 
-         $authorized = $parent instanceof FlexAuthorizeInterface
 
-             ? $parent->isAuthorized('create', 'admin', $this->user)
 
-             : $directory->isAuthorized('create', 'admin', $this->user);
 
-         if (!$authorized) {
 
-             $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'])) {
 
-             // Get default child type.
 
-             $this->data['name'] = $parent->header()->child_type ?? $parent->getBlueprint()->child_type ?? 'default';
 
-         }
 
-         if (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) {
 
-                 $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 void
 
-      * @throws RuntimeException
 
-      */
 
-     protected function taskCopy(): void
 
-     {
 
-         try {
 
-             $directory = $this->getDirectory();
 
-             if (!$directory) {
 
-                 throw new RuntimeException('Not Found', 404);
 
-             }
 
-             $object = $this->getObject();
 
-             if (!$object || !$object->exists() || !is_callable([$object, 'createCopy'])) {
 
-                 throw new RuntimeException('Not Found', 404);
 
-             }
 
-             // Pages are a special case.
 
-             $parent = $object instanceof PageInterface ? $object->parent() : $object;
 
-             $authorized = $parent instanceof FlexAuthorizeInterface
 
-                 ? $parent->isAuthorized('create', 'admin', $this->user)
 
-                 : $directory->isAuthorized('create', 'admin', $this->user);
 
-             if (!$authorized || !$parent) {
 
-                 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);
 
-         }
 
-     }
 
-     /**
 
-      * $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($data['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 void
 
-      */
 
-     public function taskSaveas(): void
 
-     {
 
-         $this->taskSave();
 
-     }
 
-     /**
 
-      * @return void
 
-      */
 
-     public function taskSave(): void
 
-     {
 
-         $directory = $this->getDirectory();
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         $key = $this->id;
 
-         try {
 
-             $object = $this->getObject($key);
 
-             if (!$object) {
 
-                 throw new RuntimeException('Not Found', 404);
 
-             }
 
-             $authorized = $object instanceof FlexAuthorizeInterface
 
-                 ? $object->isAuthorized('save', 'admin', $this->user)
 
-                 : $directory->isAuthorized($object->exists() ? 'update' : 'create', 'admin', $this->user);
 
-             if (!$authorized) {
 
-                 throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.',
 
-                         403);
 
-             }
 
-             /** @var ServerRequestInterface $request */
 
-             $request = $this->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']);
 
-                 }
 
-                 if (is_callable([$object, 'check'])) {
 
-                     $object->check($this->user);
 
-                 }
 
-                 $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';
 
-                 $this->grav['session']->post_entries_save = $postAction;
 
-                 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));
 
-             }
 
-             $this->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);
 
-                     if ($data instanceof Data) {
 
-                         $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 void
 
-      */
 
-     public function taskConfigure(): void
 
-     {
 
-         $directory = $this->getDirectory();
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         try {
 
-             $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);
 
-             }
 
-             /** @var ServerRequestInterface $request */
 
-             $request = $this->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);
 
-         }
 
-     }
 
-     /**
 
-      * Used in 3rd party editors (e.g. next-gen).
 
-      *
 
-      * @return ResponseInterface
 
-      */
 
-     public function actionConvertUrls(): ResponseInterface
 
-     {
 
-         $directory = $this->getDirectory();
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         $key = $this->id;
 
-         $object = $this->getObject($key);
 
-         if (!$object instanceof PageInterface) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         $authorized = $object instanceof FlexAuthorizeInterface
 
-             ? $object->isAuthorized('read', 'admin', $this->user)
 
-             : $directory->isAuthorized($object->exists() ? 'read' : 'create', 'admin', $this->user);
 
-         if (!$authorized) {
 
-             throw new RuntimeException($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.',
 
-                 403);
 
-         }
 
-         $request = $this->getRequest();
 
-         $data = $request->getParsedBody();
 
-         $data['data'] = json_decode($data['data'] ?? '{}', true, 512, JSON_THROW_ON_ERROR);
 
-         if (!isset($data['data'])) {
 
-             throw new RequestException($request, 'Bad Request', 400);
 
-         }
 
-         $converted_links = [];
 
-         foreach ($data['data']['a'] ?? [] as $link) {
 
-             $converted_links[$link] = Excerpts::processLinkHtml($link, $object);
 
-         }
 
-         $converted_images = [];
 
-         foreach ($data['data']['img'] ?? [] as $image) {
 
-             $converted_images[$image] = Excerpts::processImageHtml($image, $object);
 
-         }
 
-         $json = [
 
-             'status'  => 'success',
 
-             'message' => 'All links converted',
 
-             'data' => ['links' => $converted_links, 'images' => $converted_images]
 
-         ];
 
-         return $this->createJsonResponse($json, 200);
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskMediaList(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('action', 'media.list');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskMediaUpload(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('task', 'media.upload');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskMediaUploadMeta(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('task', 'media.upload.meta');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskMediaReorder(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('task', 'media.reorder');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskMediaDelete(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('task', 'media.delete');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskListmedia(): ResponseInterface
 
-     {
 
-         return $this->taskMediaList();
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskAddmedia(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('task', 'media.copy');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskDelmedia(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('task', 'media.remove');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      * @deprecated Do not use
 
-      */
 
-     public function taskFilesUpload(): ResponseInterface
 
-     {
 
-         throw new RuntimeException('Task filesUpload should not be called!');
 
-     }
 
-     /**
 
-      * @param string|null $filename
 
-      * @return ResponseInterface
 
-      * @deprecated Do not use
 
-      */
 
-     public function taskRemoveMedia($filename = null): ResponseInterface
 
-     {
 
-         throw new RuntimeException('Task removeMedia should not be called!');
 
-     }
 
-     /**
 
-      * @return ResponseInterface
 
-      */
 
-     public function taskGetFilesInFolder(): ResponseInterface
 
-     {
 
-         return $this->forwardMediaTask('action', 'media.picker');
 
-     }
 
-     /**
 
-      * @param string $type
 
-      * @param string $name
 
-      * @return ResponseInterface
 
-      */
 
-     protected function forwardMediaTask(string $type, string $name): ResponseInterface
 
-     {
 
-         $directory = $this->getDirectory();
 
-         if (!$directory) {
 
-             throw new RuntimeException('Not Found', 404);
 
-         }
 
-         $route = Uri::getCurrentRoute()->withGravParam('task', null);
 
-         $object = $this->getObject();
 
-         /** @var ServerRequest $request */
 
-         $request = $this->grav['request'];
 
-         $request = $request
 
-             ->withAttribute($type, $name)
 
-             ->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 = $this->grav['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;
 
-         /** @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'])) {
 
-             $data = $post['data'];
 
-             if (is_string($data)) {
 
-                 $data = json_decode($data, true);
 
-             }
 
-             $this->data = $this->getPost($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'];
 
-     }
 
- }
 
 
  |