admin.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350
  1. <?php
  2. namespace Grav\Plugin;
  3. use Composer\Autoload\ClassLoader;
  4. use Grav\Common\Cache;
  5. use Grav\Common\Data\Data;
  6. use Grav\Common\Debugger;
  7. use Grav\Common\File\CompiledYamlFile;
  8. use Grav\Common\Grav;
  9. use Grav\Common\Helpers\LogViewer;
  10. use Grav\Common\Inflector;
  11. use Grav\Common\Language\Language;
  12. use Grav\Common\Page\Interfaces\PageInterface;
  13. use Grav\Common\Page\Page;
  14. use Grav\Common\Page\Pages;
  15. use Grav\Common\Plugin;
  16. use Grav\Common\Plugins;
  17. use Grav\Common\Processors\Events\RequestHandlerEvent;
  18. use Grav\Common\Session;
  19. use Grav\Common\Twig\Twig;
  20. use Grav\Common\Uri;
  21. use Grav\Common\User\Interfaces\UserInterface;
  22. use Grav\Common\Utils;
  23. use Grav\Common\Yaml;
  24. use Grav\Events\PermissionsRegisterEvent;
  25. use Grav\Framework\Acl\PermissionsReader;
  26. use Grav\Framework\Psr7\Response;
  27. use Grav\Framework\Session\Exceptions\SessionException;
  28. use Grav\Plugin\Admin\Admin;
  29. use Grav\Plugin\Admin\AdminFormFactory;
  30. use Grav\Plugin\Admin\Popularity;
  31. use Grav\Plugin\Admin\Router;
  32. use Grav\Plugin\Admin\Themes;
  33. use Grav\Plugin\Admin\AdminController;
  34. use Grav\Plugin\Admin\Twig\AdminTwigExtension;
  35. use Grav\Plugin\Admin\WhiteLabel;
  36. use Grav\Plugin\Form\Form;
  37. use Grav\Plugin\Form\Forms;
  38. use Grav\Plugin\Login\Login;
  39. use Pimple\Container;
  40. use Psr\Http\Message\ResponseInterface;
  41. use RocketTheme\Toolbox\Event\Event;
  42. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  43. /**
  44. * Class AdminPlugin
  45. * @package Grav\Plugin\Admin
  46. */
  47. class AdminPlugin extends Plugin
  48. {
  49. public $features = [
  50. 'blueprints' => 1000,
  51. ];
  52. /** @var bool */
  53. protected $active = false;
  54. /** @var string */
  55. protected $template;
  56. /** @var string */
  57. protected $theme;
  58. /** @var string */
  59. protected $route;
  60. /** @var string */
  61. protected $admin_route;
  62. /** @var Uri */
  63. protected $uri;
  64. /** @var Admin */
  65. protected $admin;
  66. /** @var Session */
  67. protected $session;
  68. /** @var Popularity */
  69. protected $popularity;
  70. /** @var string */
  71. protected $base;
  72. /** @var string */
  73. protected $version;
  74. /**
  75. * @return array
  76. */
  77. public static function getSubscribedEvents()
  78. {
  79. return [
  80. 'onPluginsInitialized' => [
  81. ['setup', 100000],
  82. ['onPluginsInitialized', 1001]
  83. ],
  84. 'onRequestHandlerInit' => [
  85. ['onRequestHandlerInit', 100000]
  86. ],
  87. 'onFormRegisterTypes' => ['onFormRegisterTypes', 0],
  88. 'onPageInitialized' => ['onPageInitialized', 0],
  89. 'onShutdown' => ['onShutdown', 1000],
  90. PermissionsRegisterEvent::class => ['onRegisterPermissions', 1000],
  91. ];
  92. }
  93. /**
  94. * Get list of form field types specified in this plugin. Only special types needs to be listed.
  95. *
  96. * @return array
  97. */
  98. public function getFormFieldTypes()
  99. {
  100. return [
  101. 'column' => [
  102. 'input@' => false
  103. ],
  104. 'columns' => [
  105. 'input@' => false
  106. ],
  107. 'fieldset' => [
  108. 'input@' => false
  109. ],
  110. 'section' => [
  111. 'input@' => false
  112. ],
  113. 'list' => [
  114. 'array' => true
  115. ],
  116. 'elements' => [
  117. 'input@' => true
  118. ],
  119. 'element' => [
  120. 'input@' => false
  121. ],
  122. 'file' => [
  123. 'array' => true,
  124. 'media_field' => true,
  125. 'validate' => [
  126. 'type' => 'ignore'
  127. ]
  128. ],
  129. 'pagemedia' => [
  130. 'array' => true,
  131. 'media_field' => true,
  132. 'validate' => [
  133. 'type' => 'ignore'
  134. ]
  135. ],
  136. 'filepicker' => [
  137. 'media_picker_field' => true
  138. ],
  139. 'pagemediaselect' => [
  140. 'media_picker_field' => true
  141. ],
  142. 'permissions' => [
  143. 'ignore_empty' => true,
  144. 'validate' => [
  145. 'type' => 'array'
  146. ],
  147. 'filter' => [
  148. 'type' => 'flatten_array',
  149. 'value_type' => 'bool',
  150. ]
  151. ],
  152. 'acl_picker' => [
  153. 'ignore_empty' => true,
  154. 'validate' => [
  155. 'type' => 'array'
  156. ],
  157. 'filter' => [
  158. 'type' => 'array',
  159. 'key_type' => 'string',
  160. 'value_type' => 'bool',
  161. ]
  162. ],
  163. 'taxonomy' => [
  164. 'multiple' => true,
  165. 'validate' => [
  166. 'type' => 'array'
  167. ]
  168. ]
  169. ];
  170. }
  171. /**
  172. * @return ClassLoader
  173. */
  174. public function autoload(): ClassLoader
  175. {
  176. return require __DIR__ . '/vendor/autoload.php';
  177. }
  178. /**
  179. * @param Event $event
  180. * @return void
  181. */
  182. public function onFormRegisterTypes(Event $event): void
  183. {
  184. /** @var Forms $forms */
  185. $forms = $event['forms'];
  186. $forms->registerType('admin', new AdminFormFactory());
  187. }
  188. /**
  189. * [onPluginsInitialized:100000]
  190. *
  191. * If the admin path matches, initialize the Login plugin configuration and set the admin
  192. * as active.
  193. *
  194. * @return void
  195. */
  196. public function setup()
  197. {
  198. // Only enable admin if it has a route.
  199. $route = $this->config->get('plugins.admin.route');
  200. if (!$route) {
  201. return;
  202. }
  203. /** @var Uri uri */
  204. $this->uri = $this->grav['uri'];
  205. $this->base = '/' . trim($route, '/');
  206. $this->admin_route = rtrim($this->grav['pages']->base(), '/') . $this->base;
  207. $inAdmin = $this->isAdminPath();
  208. // If no users found, go to register.
  209. if (!$inAdmin && !Admin::doAnyUsersExist()) {
  210. $this->grav->redirect($this->admin_route);
  211. }
  212. // Only setup admin if we're inside the admin path.
  213. if ($inAdmin) {
  214. $this->setupAdmin();
  215. }
  216. }
  217. /**
  218. * [onPluginsInitialized:1001]
  219. *
  220. * If the admin plugin is set as active, initialize the admin
  221. *
  222. * @return void
  223. */
  224. public function onPluginsInitialized()
  225. {
  226. // Only activate admin if we're inside the admin path.
  227. if ($this->active) {
  228. $this->initializeAdmin();
  229. }
  230. // Always initialize popularity.
  231. $this->popularity = new Popularity();
  232. }
  233. /**
  234. * [onRequestHandlerInit:100000]
  235. *
  236. * @param RequestHandlerEvent $event
  237. * @return void
  238. */
  239. public function onRequestHandlerInit(RequestHandlerEvent $event)
  240. {
  241. // Store this version.
  242. $this->version = $this->getBlueprint()->get('version');
  243. $route = $event->getRoute();
  244. $base = $route->getRoute(0, 1);
  245. if ($base === $this->base) {
  246. /** @var Debugger $debugger */
  247. $debugger = $this->grav['debugger'];
  248. $debugger->addMessage('Admin v' . $this->version);
  249. $event->addMiddleware('admin_router', new Router($this->grav, $this->admin));
  250. }
  251. }
  252. /**
  253. * @param Event $event
  254. * @return void
  255. */
  256. public function onAdminControllerInit(Event $event): void
  257. {
  258. $eventController = $event['controller'];
  259. // Blacklist login related views.
  260. $loginViews = ['login', 'forgot', 'register', 'reset'];
  261. $eventController->blacklist_views = array_merge($eventController->blacklist_views, $loginViews);
  262. }
  263. /**
  264. * Force compile during save if admin plugin save
  265. *
  266. * @param Event $event
  267. * @return void
  268. */
  269. public function onAdminSave(Event $event)
  270. {
  271. $obj = $event['object'];
  272. if ($obj instanceof Data
  273. && ($blueprint = $obj->blueprints()) && $blueprint && $blueprint->getFilename() === 'admin/blueprints') {
  274. [$status, $msg] = $this->grav['admin-whitelabel']->compilePresetScss($obj);
  275. if (!$status) {
  276. $this->grav['messages']->add($msg, 'error');
  277. }
  278. }
  279. }
  280. /**
  281. * [onPageInitialized:0]
  282. *
  283. * @return void
  284. */
  285. public function onPageInitialized()
  286. {
  287. $template = $this->uri->param('tmpl');
  288. if ($template) {
  289. /** @var PageInterface $page */
  290. $page = $this->grav['page'];
  291. $page->template($template);
  292. }
  293. }
  294. /**
  295. * [onShutdown:1000]
  296. *
  297. * Handles the shutdown
  298. *
  299. * @return void
  300. */
  301. public function onShutdown()
  302. {
  303. if ($this->active) {
  304. //only activate when Admin is active
  305. if ($this->admin->shouldLoadAdditionalFilesInBackground()) {
  306. $this->admin->loadAdditionalFilesInBackground();
  307. }
  308. } elseif ($this->popularity && $this->config->get('plugins.admin.popularity.enabled')) {
  309. //if popularity is enabled, track non-admin hits
  310. $this->popularity->trackHit();
  311. }
  312. }
  313. /**
  314. * [onAdminDashboard:0]
  315. *
  316. * @return void
  317. */
  318. public function onAdminDashboard()
  319. {
  320. $lang = $this->grav['language'];
  321. $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
  322. 'name' => $lang->translate('PLUGIN_ADMIN.MAINTENANCE'),
  323. 'template' => 'dashboard-maintenance',
  324. ];
  325. $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
  326. 'name' => $lang->translate('PLUGIN_ADMIN.VIEWS_STATISTICS'),
  327. 'template' => 'dashboard-statistics',
  328. ];
  329. $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
  330. 'name' => $lang->translate('PLUGIN_ADMIN.NOTIFICATIONS'),
  331. 'template' => 'dashboard-notifications',
  332. ];
  333. $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
  334. 'name' => $lang->translate('PLUGIN_ADMIN.NEWS_FEED'),
  335. 'template' => 'dashboard-feed',
  336. ];
  337. $this->grav['twig']->plugins_hooked_dashboard_widgets_main[] = [
  338. 'name' => $lang->translate('PLUGIN_ADMIN.LATEST_PAGE_UPDATES'),
  339. 'template' => 'dashboard-pages',
  340. ];
  341. }
  342. /**
  343. * [onAdminTools:0]
  344. *
  345. * Provide the tools for the Tools page, currently only direct install
  346. *
  347. * @return void
  348. */
  349. public function onAdminTools(Event $event)
  350. {
  351. $event['tools'] = array_merge($event['tools'], [
  352. 'backups' => [['admin.maintenance', 'admin.super'], 'PLUGIN_ADMIN.BACKUPS'],
  353. 'scheduler' => [['admin.super'], 'PLUGIN_ADMIN.SCHEDULER'],
  354. 'logs' => [['admin.super'], 'PLUGIN_ADMIN.LOGS'],
  355. 'reports' => [['admin.super'], 'PLUGIN_ADMIN.REPORTS'],
  356. 'direct-install' => [['admin.super'], 'PLUGIN_ADMIN.DIRECT_INSTALL'],
  357. ]);
  358. }
  359. /**
  360. * Sets longer path to the home page allowing us to have list of pages when we enter to pages section.
  361. *
  362. * @return void
  363. */
  364. public function onPagesInitialized()
  365. {
  366. $config = $this->config;
  367. // Force SSL with redirect if required
  368. if ($config->get('system.force_ssl')) {
  369. if (!isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) !== 'on') {
  370. Admin::DEBUG && Admin::addDebugMessage('Admin SSL forced on, redirect');
  371. $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  372. $this->grav->redirect($url);
  373. }
  374. }
  375. $this->session = $this->grav['session'];
  376. // set session variable if it's passed via the url
  377. if (!$this->session->user->authorize('admin.super') || $this->uri->param('mode') === 'normal') {
  378. $this->session->expert = false;
  379. } elseif ($this->uri->param('mode') === 'expert') {
  380. $this->session->expert = true;
  381. } else {
  382. // set the default if not set before
  383. $this->session->expert = $this->session->expert ?? false;
  384. }
  385. // make sure page is not frozen!
  386. unset($this->grav['page']);
  387. // Call the controller if it has been set.
  388. $adminParams = $this->admin->request->getAttribute('admin');
  389. $page = null;
  390. if (isset($adminParams['controller'])) {
  391. $controllerParams = $adminParams['controller'];
  392. $class = $controllerParams['class'];
  393. if (!class_exists($class)) {
  394. throw new \RuntimeException(sprintf('Admin controller %s does not exist', $class));
  395. }
  396. /** @var \Grav\Plugin\Admin\Controllers\AdminController $controller */
  397. $controller = new $class($this->grav);
  398. $method = $controllerParams['method'];
  399. $params = $controllerParams['params'] ?? [];
  400. if (!is_callable([$controller, $method])) {
  401. throw new \RuntimeException(sprintf('Admin controller method %s() does not exist', $method));
  402. }
  403. /** @var ResponseInterface $response */
  404. $response = $controller->{$method}(...$params);
  405. if ($response->getStatusCode() !== 418) {
  406. $this->grav->close($response);
  407. }
  408. $page = $controller->getPage();
  409. if (!$page) {
  410. throw new \RuntimeException('Not Found', 404);
  411. }
  412. $this->grav['page'] = $page;
  413. $this->admin->form = $controller->getActiveForm();
  414. $legacyController = false;
  415. } else {
  416. $legacyController = true;
  417. }
  418. /** @var UserInterface $user */
  419. $user = $this->grav['user'];
  420. // Replace page service with admin.
  421. if (empty($this->grav['page'])) {
  422. $this->grav['page'] = function () use ($user) {
  423. $page = new Page();
  424. // Plugins may not have the correct Cache-Control header set, force no-store for the proxies.
  425. $page->expires(0);
  426. if ($user->authorize('admin.login')) {
  427. $event = new Event(['page' => $page]);
  428. $event = $this->grav->fireEvent('onAdminPage', $event);
  429. /** @var PageInterface $page */
  430. $page = $event['page'];
  431. if ($page->slug()) {
  432. Admin::DEBUG && Admin::addDebugMessage('Admin page: from event');
  433. return $page;
  434. }
  435. }
  436. // Look in the pages provided by the Admin plugin itself
  437. if (file_exists(__DIR__ . "/pages/admin/{$this->template}.md")) {
  438. Admin::DEBUG && Admin::addDebugMessage("Admin page: {$this->template}");
  439. $page->init(new \SplFileInfo(__DIR__ . "/pages/admin/{$this->template}.md"));
  440. $page->slug(Utils::basename($this->template));
  441. return $page;
  442. }
  443. /** @var UniformResourceLocator $locator */
  444. $locator = $this->grav['locator'];
  445. // If not provided by Admin, lookup pages added by other plugins
  446. /** @var Plugins $plugins */
  447. $plugins = $this->grav['plugins'];
  448. foreach ($plugins as $plugin) {
  449. if ($this->config->get("plugins.{$plugin->name}.enabled") !== true) {
  450. continue;
  451. }
  452. $path = $locator->findResource("plugins://{$plugin->name}/admin/pages/{$this->template}.md");
  453. if ($path) {
  454. Admin::DEBUG && Admin::addDebugMessage("Admin page: plugin {$plugin->name}/{$this->template}");
  455. $page->init(new \SplFileInfo($path));
  456. $page->slug(Utils::basename($this->template));
  457. return $page;
  458. }
  459. }
  460. return null;
  461. };
  462. }
  463. if (empty($this->grav['page'])) {
  464. if ($user->authenticated) {
  465. Admin::DEBUG && Admin::addDebugMessage('Admin page: fire onPageNotFound event');
  466. $event = new Event(['page' => null]);
  467. $event->page = null;
  468. $event = $this->grav->fireEvent('onPageNotFound', $event);
  469. /** @var PageInterface $page */
  470. $page = $event->page;
  471. if (!$page || !$page->routable()) {
  472. Admin::DEBUG && Admin::addDebugMessage('Admin page: 404 Not Found');
  473. $error_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/error.md');
  474. $page = new Page();
  475. $page->init(new \SplFileInfo($error_file));
  476. $page->slug(Utils::basename($this->route));
  477. $page->routable(true);
  478. }
  479. unset($this->grav['page']);
  480. $this->grav['page'] = $page;
  481. } else {
  482. Admin::DEBUG && Admin::addDebugMessage('Admin page: login');
  483. // Not Found and not logged in: Display login page.
  484. $login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md');
  485. $page = new Page();
  486. $page->init(new \SplFileInfo($login_file));
  487. $page->slug(Utils::basename($this->route));
  488. unset($this->grav['page']);
  489. $this->grav['page'] = $page;
  490. }
  491. }
  492. if ($legacyController) {
  493. // Handle tasks.
  494. $this->admin->task = $task = $this->grav['task'] ?? $this->grav['action'];
  495. if ($task) {
  496. Admin::DEBUG && Admin::addDebugMessage("Admin task: {$task}");
  497. // Make local copy of POST.
  498. $post = $this->grav['uri']->post();
  499. $this->initializeController($task, $post);
  500. } elseif ($this->template === 'logs' && $this->route) {
  501. // Display RAW error message.
  502. $response = new Response(200, [], $this->admin->logEntry());
  503. $this->grav->close($response);
  504. }
  505. }
  506. // Explicitly set a timestamp on assets
  507. $this->grav['assets']->setTimestamp(substr(md5(GRAV_VERSION . $this->grav['config']->checksum()), 0, 10));
  508. }
  509. /**
  510. * Handles initializing the assets
  511. *
  512. * @return void
  513. */
  514. public function onAssetsInitialized()
  515. {
  516. // Disable Asset pipelining
  517. $assets = $this->grav['assets'];
  518. $assets->setJsPipeline(false);
  519. $assets->setCssPipeline(false);
  520. }
  521. /**
  522. * Add twig paths to plugin templates.
  523. *
  524. * @return void
  525. */
  526. public function onTwigTemplatePaths()
  527. {
  528. $twig_paths = [];
  529. $this->grav->fireEvent('onAdminTwigTemplatePaths', new Event(['paths' => &$twig_paths]));
  530. $twig_paths[] = __DIR__ . '/themes/' . $this->theme . '/templates';
  531. $this->grav['twig']->twig_paths = $twig_paths;
  532. }
  533. /**
  534. * Set all twig variables for generating output.
  535. *
  536. * @return void
  537. */
  538. public function onTwigSiteVariables()
  539. {
  540. /** @var Twig $twig */
  541. $twig = $this->grav['twig'];
  542. /** @var PageInterface $page */
  543. $page = $this->grav['page'];
  544. $twig->twig_vars['location'] = $this->template;
  545. $twig->twig_vars['nav_route'] = trim($this->template . '/' . $this->route, '/') . '/';
  546. $twig->twig_vars['base_url_relative_frontend'] = $twig->twig_vars['base_url_relative'] ?: '/';
  547. $twig->twig_vars['admin_route'] = trim($this->admin_route, '/');
  548. $twig->twig_vars['template_route'] = $this->template;
  549. $twig->twig_vars['current_route'] = '/' . $twig->twig_vars['admin_route'] . '/' . $this->template . '/' . $this->route;
  550. $twig->twig_vars['base_url_relative'] = $twig->twig_vars['base_url_simple'] . '/' . $twig->twig_vars['admin_route'];
  551. $twig->twig_vars['current_url'] = rtrim($twig->twig_vars['base_url_relative'] . '/' . $this->template . '/' . $this->route, '/');
  552. $theme_url = '/' . ltrim($this->grav['locator']->findResource('plugin://admin/themes/' . $this->theme,
  553. false), '/');
  554. $twig->twig_vars['theme_url'] = $theme_url;
  555. $twig->twig_vars['base_url'] = $twig->twig_vars['base_url_relative'];
  556. $twig->twig_vars['base_path'] = GRAV_ROOT;
  557. $twig->twig_vars['admin'] = $this->admin;
  558. $twig->twig_vars['user'] = $this->admin->user;
  559. $twig->twig_vars['admin_version'] = $this->version;
  560. $twig->twig_vars['logviewer'] = new LogViewer();
  561. $twig->twig_vars['form_max_filesize'] = Utils::getUploadLimit() / 1024 / 1024;
  562. // Start white label functionality
  563. $twig->twig_vars['whitelabel_presets'] = $this->getPresets();
  564. $custom_logo = $this->config->get('plugins.admin.whitelabel.logo_custom', false);
  565. $custom_login_logo = $this->config->get('plugins.admin.whitelabel.logo_login', false);
  566. $custom_footer = $this->config->get('plugins.admin.whitelabel.custom_footer', false);
  567. if ($custom_logo && is_array($custom_logo)) {
  568. $custom_logo = array_keys($custom_logo);
  569. $path = array_shift($custom_logo);
  570. $twig->twig_vars['custom_admin_logo'] = $path;
  571. }
  572. if ($custom_login_logo && is_array($custom_login_logo)) {
  573. $custom_login_logo = array_keys($custom_login_logo);
  574. $path = array_shift($custom_login_logo);
  575. $twig->twig_vars['custom_login_logo'] = $path;
  576. }
  577. if ($custom_footer) {
  578. $footer = Utils::processMarkdown($custom_footer);
  579. $twig->twig_vars['custom_admin_footer'] = $footer;
  580. }
  581. $custom_css = $this->config->get('plugins.admin.whitelabel.custom_css', false);
  582. if ($custom_css) {
  583. $this->grav['assets']->addInlineCss($custom_css);
  584. }
  585. // End white label functionality
  586. $fa_icons_file = CompiledYamlFile::instance($this->grav['locator']->findResource('plugin://admin/themes/grav/templates/forms/fields/iconpicker/icons' . YAML_EXT));
  587. $fa_icons = $fa_icons_file->content();
  588. $fa_icons = array_map(function ($icon) {
  589. //only pick used values
  590. return ['id' => $icon['id'], 'unicode' => $icon['unicode']];
  591. }, $fa_icons['icons']);
  592. $twig->twig_vars['fa_icons'] = $fa_icons;
  593. // add form if it exists in the page
  594. $header = $page->header();
  595. $forms = [];
  596. if (isset($header->forms)) foreach ($header->forms as $key => $form) {
  597. $forms[$key] = new Form($page, null, $form);
  598. }
  599. $twig->twig_vars['forms'] = $forms;
  600. // preserve form validation
  601. if ($this->admin->form) {
  602. $twig->twig_vars['form'] = $this->admin->form;
  603. } elseif (!isset($twig->twig_vars['form'])) {
  604. if (isset($header->form)) {
  605. $twig->twig_vars['form'] = new Form($page);
  606. } elseif (isset($header->forms)) {
  607. $twig->twig_vars['form'] = new Form($page, null, reset($header->forms));
  608. }
  609. }
  610. // Gather all nav items
  611. $this->grav['twig']->plugins_hooked_nav = [];
  612. $this->grav->fireEvent('onAdminMenu');
  613. uasort($this->grav['twig']->plugins_hooked_nav, function ($a, $b) {
  614. $ac = $a['priority'] ?? 0;
  615. $bc = $b['priority'] ?? 0;
  616. return $bc <=> $ac;
  617. });
  618. // Gather Plugin-hooked dashboard items
  619. $this->grav->fireEvent('onAdminDashboard');
  620. switch ($this->template) {
  621. case 'dashboard':
  622. $twig->twig_vars['popularity'] = $this->popularity;
  623. break;
  624. }
  625. $flashData = $this->grav['session']->getFlashCookieObject(Admin::TMP_COOKIE_NAME);
  626. if (isset($flashData->message)) {
  627. $this->grav['messages']->add($flashData->message, $flashData->status);
  628. }
  629. }
  630. /**
  631. * Add images to twig template paths to allow inclusion of SVG files
  632. *
  633. * @return void
  634. */
  635. public function onTwigLoader()
  636. {
  637. /** @var Twig $twig */
  638. $twig = $this->grav['twig'];
  639. /** @var UniformResourceLocator $locator */
  640. $locator = Grav::instance()['locator'];
  641. $theme_paths = $locator->findResources('plugins://admin/themes/' . $this->theme . '/images');
  642. foreach($theme_paths as $images_path) {
  643. $twig->addPath($images_path, 'admin-images');
  644. }
  645. }
  646. /**
  647. * Add the Admin Twig Extensions
  648. *
  649. * @return void
  650. */
  651. public function onTwigExtensions()
  652. {
  653. /** @var Twig $twig */
  654. $twig = $this->grav['twig'];
  655. $twig->twig->addExtension(new AdminTwigExtension);
  656. }
  657. /**
  658. * @param Event $event
  659. * @return void
  660. */
  661. public function onAdminAfterSave(Event $event)
  662. {
  663. // Special case to redirect after changing the admin route to avoid 'breaking'
  664. $obj = $event['object'];
  665. if (null !== $obj && method_exists($obj, 'blueprints')) {
  666. $blueprint = $obj->blueprints()->getFilename();
  667. if ($blueprint === 'admin/blueprints' && isset($obj->route) && $this->admin_route !== $obj->route) {
  668. $redirect = preg_replace('/^' . str_replace('/','\/',$this->admin_route) . '/',$obj->route,$this->uri->path());
  669. $this->grav->redirect($redirect);
  670. }
  671. }
  672. }
  673. /**
  674. * Convert some types where we want to process out of the standard config path
  675. *
  676. * @param Event $e
  677. * @return void
  678. */
  679. public function onAdminData(Event $e)
  680. {
  681. $type = $e['type'] ?? null;
  682. switch ($type) {
  683. case 'config':
  684. $e['type'] = $this->admin->authorize(['admin.configuration.system', 'admin.super']) ? 'config/system' : 'config/site';
  685. break;
  686. case 'tools/scheduler':
  687. $e['type'] = 'config/scheduler';
  688. break;
  689. case 'tools':
  690. case 'tools/backups':
  691. $e['type'] = 'config/backups';
  692. break;
  693. }
  694. }
  695. /**
  696. * @return void
  697. */
  698. public function onOutputGenerated()
  699. {
  700. // Clear flash objects for previously uploaded files whenever the user switches page or reloads
  701. // ignoring any JSON / extension call
  702. if ($this->admin->task !== 'save' && empty($this->uri->extension())) {
  703. // Discard any previously uploaded files session and remove all uploaded files.
  704. if ($flash = $this->session->getFlashObject('files-upload')) {
  705. $flash = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($flash));
  706. foreach ($flash as $key => $value) {
  707. if ($key !== 'tmp_name') {
  708. continue;
  709. }
  710. @unlink($value);
  711. }
  712. }
  713. }
  714. }
  715. /**
  716. * Initial stab at registering permissions (WIP)
  717. *
  718. * @param PermissionsRegisterEvent $event
  719. * @return void
  720. */
  721. public function onRegisterPermissions(PermissionsRegisterEvent $event): void
  722. {
  723. $actions = PermissionsReader::fromYaml("plugin://{$this->name}/permissions.yaml");
  724. $permissions = $event->permissions;
  725. $permissions->addActions($actions);
  726. }
  727. /**
  728. * @return void
  729. */
  730. public function onAdminMenu()
  731. {
  732. /** @var Twig $twig */
  733. $twig = $this->grav['twig'];
  734. // Dashboard
  735. $twig->plugins_hooked_nav['PLUGIN_ADMIN.DASHBOARD'] = [
  736. 'route' => 'dashboard',
  737. 'icon' => 'fa-th',
  738. 'authorize' => ['admin.login', 'admin.super'],
  739. 'priority' => 10
  740. ];
  741. // Configuration
  742. $twig->plugins_hooked_nav['PLUGIN_ADMIN.CONFIGURATION'] = [
  743. 'route' => 'config',
  744. 'icon' => 'fa-wrench',
  745. 'authorize' => [
  746. 'admin.configuration.system',
  747. 'admin.configuration.site',
  748. 'admin.configuration.media',
  749. 'admin.configuration.security',
  750. 'admin.configuration.info',
  751. 'admin.super'],
  752. 'priority' => 9
  753. ];
  754. // Pages
  755. $count = new Container(['count' => function () { return $this->admin->pagesCount(); }]);
  756. $twig->plugins_hooked_nav['PLUGIN_ADMIN.PAGES'] = [
  757. 'route' => 'pages',
  758. 'icon' => 'fa-file-text-o',
  759. 'authorize' => ['admin.pages', 'admin.super'],
  760. 'badge' => $count,
  761. 'priority' => 5
  762. ];
  763. // Plugins
  764. $count = new Container(['updates' => 0, 'count' => function () { return count($this->admin->plugins()); }]);
  765. $twig->plugins_hooked_nav['PLUGIN_ADMIN.PLUGINS'] = [
  766. 'route' => 'plugins',
  767. 'icon' => 'fa-plug',
  768. 'authorize' => ['admin.plugins', 'admin.super'],
  769. 'badge' => $count,
  770. 'priority' => -8
  771. ];
  772. // Themes
  773. $count = new Container(['updates' => 0, 'count' => function () { return count($this->admin->themes()); }]);
  774. $twig->plugins_hooked_nav['PLUGIN_ADMIN.THEMES'] = [
  775. 'route' => 'themes',
  776. 'icon' => 'fa-tint',
  777. 'authorize' => ['admin.themes', 'admin.super'],
  778. 'badge' => $count,
  779. 'priority' => -9
  780. ];
  781. // Tools
  782. $twig->plugins_hooked_nav['PLUGIN_ADMIN.TOOLS'] = [
  783. 'route' => 'tools',
  784. 'icon' => 'fa-briefcase',
  785. 'authorize' => $this->admin::toolsPermissions(),
  786. 'priority' => -10
  787. ];
  788. }
  789. /**
  790. * Check if the current route is under the admin path
  791. *
  792. * @return bool
  793. */
  794. public function isAdminPath()
  795. {
  796. $route = $this->uri->route();
  797. return $route === $this->base || 0 === strpos($route, $this->base . '/');
  798. }
  799. /**
  800. * Helper function to replace Pages::Types() and to provide an event to manipulate the data
  801. *
  802. * Dispatches 'onAdminPageTypes' event with 'types' data member which is a reference to the data
  803. *
  804. * @param bool $keysOnly
  805. * @return array
  806. */
  807. public static function pagesTypes(bool $keysOnly = false)
  808. {
  809. $types = Pages::types();
  810. // First filter by configuration
  811. $hideTypes = (array)Grav::instance()['config']->get('plugins.admin.hide_page_types');
  812. foreach ($hideTypes as $hide) {
  813. if (isset($types[$hide])) {
  814. unset($types[$hide]);
  815. } else {
  816. foreach ($types as $key => $type) {
  817. $match = preg_match('#' . $hide . '#i', $key);
  818. if ($match) {
  819. unset($types[$key]);
  820. }
  821. }
  822. }
  823. }
  824. // Allow manipulating of the data by event
  825. $e = new Event(['types' => &$types]);
  826. Grav::instance()->fireEvent('onAdminPageTypes', $e);
  827. return $keysOnly ? array_keys($types) : $types;
  828. }
  829. /**
  830. * Helper function to replace Pages::modularTypes() and to provide an event to manipulate the data
  831. *
  832. * Dispatches 'onAdminModularPageTypes' event with 'types' data member which is a reference to the data
  833. *
  834. * @param bool $keysOnly
  835. * @return array
  836. */
  837. public static function pagesModularTypes(bool $keysOnly = false)
  838. {
  839. $types = Pages::modularTypes();
  840. // First filter by configuration
  841. $hideTypes = (array)Grav::instance()['config']->get('plugins.admin.hide_modular_page_types');
  842. foreach ($hideTypes as $hide) {
  843. if (isset($types[$hide])) {
  844. unset($types[$hide]);
  845. } else {
  846. foreach ($types as $key => $type) {
  847. $match = preg_match('#' . $hide . '#i', $key);
  848. if ($match) {
  849. unset($types[$key]);
  850. }
  851. }
  852. }
  853. }
  854. // Allow manipulating of the data by event
  855. $e = new Event(['types' => &$types]);
  856. Grav::instance()->fireEvent('onAdminModularPageTypes', $e);
  857. return $keysOnly ? array_keys($types) : $types;
  858. }
  859. /**
  860. * Validate a value. Currently validates
  861. *
  862. * - 'user' for username format and username availability.
  863. * - 'password1' for password format
  864. * - 'password2' for equality to password1
  865. *
  866. * @param string $type The field type
  867. * @param string $value The field value
  868. * @param string $extra Any extra value required
  869. *
  870. * @return bool
  871. */
  872. protected function validate($type, $value, $extra = '')
  873. {
  874. /** @var Login $login */
  875. $login = $this->grav['login'];
  876. return $login->validateField($type, $value, $extra);
  877. }
  878. /**
  879. * @param string $task
  880. * @param array|null $post
  881. * @return void
  882. */
  883. protected function initializeController($task, $post = null): void
  884. {
  885. Admin::DEBUG && Admin::addDebugMessage('Admin controller: execute');
  886. $controller = new AdminController();
  887. $controller->initialize($this->grav, $this->template, $task, $this->route, $post);
  888. $controller->execute();
  889. $controller->redirect();
  890. }
  891. /**
  892. * @return void
  893. */
  894. protected function setupAdmin()
  895. {
  896. // Set cache based on admin_cache option.
  897. /** @var Cache $cache */
  898. $cache = $this->grav['cache'];
  899. $cache->setEnabled($this->config->get('plugins.admin.cache_enabled'));
  900. /** @var Pages $pages */
  901. $pages = $this->grav['pages'];
  902. // Disable frontend pages in admin.
  903. $pages->disablePages();
  904. // Force file hash checks to fix caching on moved and deleted pages.
  905. $pages->setCheckMethod('hash');
  906. /** @var Session $session */
  907. $session = $this->grav['session'];
  908. // Make sure that the session has been initialized.
  909. try {
  910. $session->init();
  911. } catch (SessionException $e) {
  912. $session->init();
  913. $message = 'Session corruption detected, restarting session...';
  914. /** @var Debugger $debugger */
  915. $debugger = $this->grav['debugger'];
  916. $debugger->addMessage($message);
  917. $this->grav['messages']->add($message, 'error');
  918. }
  919. $this->active = true;
  920. }
  921. /**
  922. * Initialize the admin.
  923. *
  924. * @return void
  925. * @throws \RuntimeException
  926. */
  927. protected function initializeAdmin()
  928. {
  929. // Check for required plugins
  930. if (!$this->grav['config']->get('plugins.login.enabled') || !$this->grav['config']->get('plugins.form.enabled') || !$this->grav['config']->get('plugins.email.enabled')) {
  931. throw new \RuntimeException('One of the required plugins is missing or not enabled');
  932. }
  933. /** @var Cache $cache */
  934. $cache = $this->grav['cache'];
  935. // Have a unique Admin-only Cache key
  936. $cache_key = $cache->getKey();
  937. $cache->setKey($cache_key . '$');
  938. /** @var Session $session */
  939. $session = $this->grav['session'];
  940. /** @var Language $language */
  941. $language = $this->grav['language'];
  942. /** @var UniformResourceLocator $locator */
  943. $locator = $this->grav['locator'];
  944. // Turn on Twig autoescaping
  945. if ($this->uri->param('task') !== 'processmarkdown') {
  946. $this->grav['twig']->setAutoescape(true);
  947. }
  948. // Initialize Admin Language if needed
  949. if ($language->enabled() && empty($session->admin_lang)) {
  950. $session->admin_lang = $language->getLanguage();
  951. }
  952. // Decide admin template and route.
  953. $path = trim(substr($this->uri->route(), strlen($this->base)), '/');
  954. if (empty($this->template)) {
  955. $this->template = 'dashboard';
  956. }
  957. // Can't access path directly...
  958. if ($path && $path !== 'register') {
  959. $array = explode('/', $path, 2);
  960. $this->template = array_shift($array);
  961. $this->route = array_shift($array);
  962. }
  963. // Initialize admin class (also registers it to Grav services).
  964. $this->admin = new Admin($this->grav, $this->admin_route, $this->template, $this->route);
  965. // Get theme for admin
  966. $this->theme = $this->config->get('plugins.admin.theme', 'grav');
  967. // Replace themes service with admin.
  968. $this->grav['themes'] = function () {
  969. return new Themes($this->grav);
  970. };
  971. // Initialize white label functionality
  972. $this->grav['admin-whitelabel'] = new WhiteLabel();
  973. // Compile a missing preset.css file
  974. $preset_css = 'asset://admin-preset.css';
  975. $preset_path = $this->grav['locator']->findResource($preset_css);
  976. if (!$preset_path) {
  977. $this->grav['admin-whitelabel']->compilePresetScss($this->config->get('plugins.admin.whitelabel'));
  978. }
  979. // These events are needed for login.
  980. $this->enable([
  981. 'onTwigExtensions' => ['onTwigExtensions', 1000],
  982. 'onPagesInitialized' => ['onPagesInitialized', 1000],
  983. 'onTwigLoader' => ['onTwigLoader', 1000],
  984. 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 1000],
  985. 'onTwigSiteVariables' => ['onTwigSiteVariables', 1000],
  986. 'onAssetsInitialized' => ['onAssetsInitialized', 1000],
  987. ]);
  988. // Do not do more if user isn't logged in.
  989. if (!$this->admin->user->authorize('admin.login')) {
  990. return;
  991. }
  992. // These events are not needed during login.
  993. $this->enable([
  994. 'onAdminControllerInit' => ['onAdminControllerInit', 1001],
  995. 'onAdminDashboard' => ['onAdminDashboard', 0],
  996. 'onAdminMenu' => ['onAdminMenu', 1000],
  997. 'onAdminTools' => ['onAdminTools', 0],
  998. 'onAdminSave' => ['onAdminSave', 0],
  999. 'onAdminAfterSave' => ['onAdminAfterSave', 0],
  1000. 'onAdminData' => ['onAdminData', 0],
  1001. 'onOutputGenerated' => ['onOutputGenerated', 0],
  1002. ]);
  1003. // Double check we have system.yaml, site.yaml etc
  1004. $config_path = $locator->findResource('user://config');
  1005. foreach ($this->admin::configurations() as $config_file) {
  1006. if ($config_file === 'info') {
  1007. continue;
  1008. }
  1009. $config_file = "{$config_path}/{$config_file}.yaml";
  1010. if (!file_exists($config_file)) {
  1011. touch($config_file);
  1012. }
  1013. }
  1014. $assets = $this->grav['assets'];
  1015. $translations = 'this.GravAdmin = this.GravAdmin || {}; if (!this.GravAdmin.translations) this.GravAdmin.translations = {}; ' . PHP_EOL . 'this.GravAdmin.translations.PLUGIN_ADMIN = {';
  1016. // Enable language translations
  1017. $translations_actual_state = $this->config->get('system.languages.translations');
  1018. $this->config->set('system.languages.translations', true);
  1019. $strings = [
  1020. 'EVERYTHING_UP_TO_DATE',
  1021. 'UPDATES_ARE_AVAILABLE',
  1022. 'IS_AVAILABLE_FOR_UPDATE',
  1023. 'AND',
  1024. 'IS_NOW_AVAILABLE',
  1025. 'CURRENT',
  1026. 'UPDATE_GRAV_NOW',
  1027. 'TASK_COMPLETED',
  1028. 'UPDATE',
  1029. 'UPDATING_PLEASE_WAIT',
  1030. 'GRAV_SYMBOLICALLY_LINKED',
  1031. 'OF_YOUR',
  1032. 'OF_THIS',
  1033. 'HAVE_AN_UPDATE_AVAILABLE',
  1034. 'UPDATE_AVAILABLE',
  1035. 'UPDATES_AVAILABLE',
  1036. 'FULLY_UPDATED',
  1037. 'DAYS',
  1038. 'PAGE_MODES',
  1039. 'PAGE_TYPES',
  1040. 'ACCESS_LEVELS',
  1041. 'NOTHING_TO_SAVE',
  1042. 'FILE_UNSUPPORTED',
  1043. 'FILE_ERROR_ADD',
  1044. 'FILE_ERROR_UPLOAD',
  1045. 'DROP_FILES_HERE_TO_UPLOAD',
  1046. 'DELETE',
  1047. 'UNSET',
  1048. 'INSERT',
  1049. 'METADATA',
  1050. 'VIEW',
  1051. 'UNDO',
  1052. 'REDO',
  1053. 'HEADERS',
  1054. 'BOLD',
  1055. 'ITALIC',
  1056. 'STRIKETHROUGH',
  1057. 'SUMMARY_DELIMITER',
  1058. 'LINK',
  1059. 'IMAGE',
  1060. 'BLOCKQUOTE',
  1061. 'UNORDERED_LIST',
  1062. 'ORDERED_LIST',
  1063. 'EDITOR',
  1064. 'PREVIEW',
  1065. 'FULLSCREEN',
  1066. 'MODULE',
  1067. 'NON_MODULE',
  1068. 'VISIBLE',
  1069. 'NON_VISIBLE',
  1070. 'ROUTABLE',
  1071. 'NON_ROUTABLE',
  1072. 'PUBLISHED',
  1073. 'NON_PUBLISHED',
  1074. 'PLUGINS',
  1075. 'THEMES',
  1076. 'ALL',
  1077. 'FROM',
  1078. 'TO',
  1079. 'DROPZONE_CANCEL_UPLOAD',
  1080. 'DROPZONE_CANCEL_UPLOAD_CONFIRMATION',
  1081. 'DROPZONE_DEFAULT_MESSAGE',
  1082. 'DROPZONE_FALLBACK_MESSAGE',
  1083. 'DROPZONE_FALLBACK_TEXT',
  1084. 'DROPZONE_FILE_TOO_BIG',
  1085. 'DROPZONE_INVALID_FILE_TYPE',
  1086. 'DROPZONE_MAX_FILES_EXCEEDED',
  1087. 'DROPZONE_REMOVE_FILE',
  1088. 'DROPZONE_RESPONSE_ERROR'
  1089. ];
  1090. foreach ($strings as $string) {
  1091. $separator = (end($strings) === $string) ? '' : ',';
  1092. $translations .= '"' . $string . '": "' . htmlspecialchars($this->admin::translate('PLUGIN_ADMIN.' . $string)) . '"' . $separator;
  1093. }
  1094. $translations .= '};';
  1095. $translations .= 'this.GravAdmin.translations.PLUGIN_FORM = {';
  1096. $strings = ['RESOLUTION_MIN', 'RESOLUTION_MAX'];
  1097. foreach ($strings as $string) {
  1098. $separator = (end($strings) === $string) ? '' : ',';
  1099. $translations .= '"' . $string . '": "' . $this->admin::translate('PLUGIN_FORM.' . $string) . '"' . $separator;
  1100. }
  1101. $translations .= '};';
  1102. $translations .= 'this.GravAdmin.translations.GRAV_CORE = {';
  1103. $strings = [
  1104. 'NICETIME.SECOND',
  1105. 'NICETIME.MINUTE',
  1106. 'NICETIME.HOUR',
  1107. 'NICETIME.DAY',
  1108. 'NICETIME.WEEK',
  1109. 'NICETIME.MONTH',
  1110. 'NICETIME.YEAR',
  1111. 'CRON.EVERY',
  1112. 'CRON.EVERY_HOUR',
  1113. 'CRON.EVERY_MINUTE',
  1114. 'CRON.EVERY_DAY_OF_WEEK',
  1115. 'CRON.EVERY_DAY_OF_MONTH',
  1116. 'CRON.EVERY_MONTH',
  1117. 'CRON.TEXT_PERIOD',
  1118. 'CRON.TEXT_MINS',
  1119. 'CRON.TEXT_TIME',
  1120. 'CRON.TEXT_DOW',
  1121. 'CRON.TEXT_MONTH',
  1122. 'CRON.TEXT_DOM',
  1123. 'CRON.ERROR1',
  1124. 'CRON.ERROR2',
  1125. 'CRON.ERROR3',
  1126. 'CRON.ERROR4',
  1127. 'MONTHS_OF_THE_YEAR',
  1128. 'DAYS_OF_THE_WEEK'
  1129. ];
  1130. foreach ($strings as $string) {
  1131. $separator = (end($strings) === $string) ? '' : ',';
  1132. $translations .= '"' . $string . '": ' . json_encode($this->admin::translate('GRAV.'.$string)) . $separator;
  1133. }
  1134. $translations .= '};';
  1135. // set the actual translations state back
  1136. $this->config->set('system.languages.translations', $translations_actual_state);
  1137. $assets->addInlineJs($translations);
  1138. // Fire even to register permissions from other plugins
  1139. $this->grav->fireEvent('onAdminRegisterPermissions', new Event(['admin' => $this->admin]));
  1140. }
  1141. /**
  1142. * @return array
  1143. */
  1144. public static function themeOptions()
  1145. {
  1146. static $options;
  1147. if (null === $options) {
  1148. $options = [];
  1149. $theme_files = glob(__dir__ . '/themes/grav/css/codemirror/themes/*.css');
  1150. foreach ($theme_files as $theme_file) {
  1151. $theme = Utils::basename(Utils::basename($theme_file, '.css'));
  1152. $options[$theme] = Inflector::titleize($theme);
  1153. }
  1154. }
  1155. return $options;
  1156. }
  1157. /**
  1158. * @return array
  1159. */
  1160. public function getPresets()
  1161. {
  1162. $filename = $this->grav['locator']->findResource('plugin://admin/presets.yaml', false);
  1163. $file = CompiledYamlFile::instance($filename);
  1164. $presets = (array)$file->content();
  1165. $custom_presets = $this->config->get('plugins.admin.whitelabel.custom_presets');
  1166. if (isset($custom_presets)) {
  1167. $custom_presets = Yaml::parse($custom_presets);
  1168. if (is_array($custom_presets)) {
  1169. if (isset($custom_presets['name'], $custom_presets['colors'], $custom_presets['accents'])) {
  1170. $preset = [Inflector::hyphenize($custom_presets['name']) => $custom_presets];
  1171. $presets = $preset + $presets;
  1172. } else {
  1173. foreach ($custom_presets as $value) {
  1174. if (isset($value['name'], $value['colors'], $value['accents'])) {
  1175. $preset = [Inflector::hyphenize($value['name']) => $value];
  1176. $presets = $preset + $presets;
  1177. }
  1178. }
  1179. }
  1180. }
  1181. }
  1182. return $presets;
  1183. }
  1184. }