| 
									
										
										
										
											2021-03-26 14:39:37 +02:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @package    Grav\Plugin\Admin | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @copyright  Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved. | 
					
						
							|  |  |  |  * @license    MIT License; see LICENSE file for details. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | declare(strict_types=1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Grav\Plugin\Admin\Controllers; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | use Grav\Common\Config\Config; | 
					
						
							|  |  |  | use Grav\Common\Data\Blueprint; | 
					
						
							|  |  |  | use Grav\Common\Grav; | 
					
						
							|  |  |  | use Grav\Common\Language\Language; | 
					
						
							|  |  |  | use Grav\Common\Page\Interfaces\PageInterface; | 
					
						
							|  |  |  | use Grav\Common\Page\Page; | 
					
						
							|  |  |  | use Grav\Common\Page\Pages; | 
					
						
							|  |  |  | use Grav\Common\Uri; | 
					
						
							|  |  |  | use Grav\Common\User\Interfaces\UserInterface; | 
					
						
							|  |  |  | use Grav\Common\Utils; | 
					
						
							|  |  |  | use Grav\Framework\Controller\Traits\ControllerResponseTrait; | 
					
						
							|  |  |  | use Grav\Framework\RequestHandler\Exception\PageExpiredException; | 
					
						
							|  |  |  | use Grav\Framework\Session\SessionInterface; | 
					
						
							|  |  |  | use Grav\Plugin\Admin\Admin; | 
					
						
							|  |  |  | use Grav\Plugin\Admin\AdminForm; | 
					
						
							|  |  |  | use Psr\Http\Message\ResponseInterface; | 
					
						
							|  |  |  | use Psr\Http\Message\ServerRequestInterface; | 
					
						
							|  |  |  | use RocketTheme\Toolbox\Session\Message; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | abstract class AdminController | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     use ControllerResponseTrait { | 
					
						
							|  |  |  |         createRedirectResponse as traitCreateRedirectResponse; | 
					
						
							|  |  |  |         getErrorJson as traitGetErrorJson; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** @var string */ | 
					
						
							|  |  |  |     protected $nonce_action = 'admin-form'; | 
					
						
							|  |  |  |     /** @var string */ | 
					
						
							|  |  |  |     protected $nonce_name = 'admin-nonce'; | 
					
						
							|  |  |  |     /** @var Grav */ | 
					
						
							|  |  |  |     protected $grav; | 
					
						
							|  |  |  |     /** @var PageInterface */ | 
					
						
							|  |  |  |     protected $page; | 
					
						
							|  |  |  |     /** @var AdminForm|null */ | 
					
						
							|  |  |  |     protected $form; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function __construct(Grav $grav) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->grav = $grav; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @return PageInterface|null | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getPage(): ?PageInterface | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->page; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get currently active form. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return AdminForm|null | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getActiveForm(): ?AdminForm | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (null === $this->form) { | 
					
						
							|  |  |  |             $post = $this->getPost(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $active = $post['__form-name__'] ?? null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $this->form = $active ? $this->getForm($active) : null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $this->form; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get a form. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $name | 
					
						
							|  |  |  |      * @param array $options | 
					
						
							|  |  |  |      * @return AdminForm|null | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getForm(string $name, array $options = []): ?AdminForm | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $post = $this->getPost(); | 
					
						
							|  |  |  |         $page = $this->getPage(); | 
					
						
							|  |  |  |         $forms = $page ? $page->forms() : []; | 
					
						
							|  |  |  |         $blueprint = $forms[$name] ?? null; | 
					
						
							|  |  |  |         if (null === $blueprint) { | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $active = $post['__form-name__'] ?? null; | 
					
						
							|  |  |  |         $unique_id = $active && $active === $name ? ($post['__unique_form_id__'] ?? null) : null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $options += [ | 
					
						
							|  |  |  |             'unique_id' => $unique_id, | 
					
						
							|  |  |  |             'blueprint' => new Blueprint(null, ['form' => $blueprint]), | 
					
						
							|  |  |  |             'submit_method' => $this->getFormSubmitMethod($name), | 
					
						
							|  |  |  |             'nonce_name' => $this->nonce_name, | 
					
						
							|  |  |  |             'nonce_action' => $this->nonce_action, | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return new AdminForm($name, $options); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     abstract protected function getFormSubmitMethod(string $name): callable; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $route | 
					
						
							|  |  |  |      * @param string|null $lang | 
					
						
							|  |  |  |      * @return string | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getAdminUrl(string $route, string $lang = null): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         /** @var Pages $pages */ | 
					
						
							|  |  |  |         $pages = $this->grav['pages']; | 
					
						
							|  |  |  |         $admin = $this->getAdmin(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-15 10:11:04 +03:00
										 |  |  |         return $pages->baseUrl($lang) . $admin->base . $route; | 
					
						
							| 
									
										
										
										
											2021-03-26 14:39:37 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $route | 
					
						
							|  |  |  |      * @param string|null $lang | 
					
						
							|  |  |  |      * @return string | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getAbsoluteAdminUrl(string $route, string $lang = null): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         /** @var Pages $pages */ | 
					
						
							|  |  |  |         $pages = $this->grav['pages']; | 
					
						
							|  |  |  |         $admin = $this->getAdmin(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-15 10:11:04 +03:00
										 |  |  |         return $pages->baseUrl($lang, true) . $admin->base . $route; | 
					
						
							| 
									
										
										
										
											2021-03-26 14:39:37 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get session. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return SessionInterface | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getSession(): SessionInterface | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->grav['session']; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @return Admin | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getAdmin(): Admin | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->grav['admin']; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @return UserInterface | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getUser(): UserInterface | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->getAdmin()->user; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @return ServerRequestInterface | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getRequest(): ServerRequestInterface | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->getAdmin()->request; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getPost(): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return (array)($this->getRequest()->getParsedBody() ?? []); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Translate a string. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $string | 
					
						
							|  |  |  |      * @param mixed ...$args | 
					
						
							|  |  |  |      * @return string | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function translate(string $string, ...$args): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         /** @var Language $language */ | 
					
						
							|  |  |  |         $language = $this->grav['language']; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         array_unshift($args, $string); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $language->translate($args); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Set message to be shown in the admin. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $message | 
					
						
							|  |  |  |      * @param string $type | 
					
						
							|  |  |  |      * @return $this | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function setMessage(string $message, string $type = 'info'): AdminController | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         /** @var Message $messages */ | 
					
						
							|  |  |  |         $messages = $this->grav['messages']; | 
					
						
							|  |  |  |         $messages->add($message, $type); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $this; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @return Config | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getConfig(): Config | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->grav['config']; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Check if request nonce is valid. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return void | 
					
						
							|  |  |  |      * @throws PageExpiredException  If nonce is not valid. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function checkNonce(): void | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $nonce = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $nonce_name = $this->form ? $this->form->getNonceName() : $this->nonce_name; | 
					
						
							|  |  |  |         $nonce_action = $this->form ? $this->form->getNonceAction() : $this->nonce_action; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (\in_array(strtoupper($this->getRequest()->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) { | 
					
						
							|  |  |  |             $post = $this->getPost(); | 
					
						
							|  |  |  |             $nonce = $post[$nonce_name] ?? null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /** @var Uri $uri */ | 
					
						
							|  |  |  |         $uri = $this->grav['uri']; | 
					
						
							|  |  |  |         if (!$nonce) { | 
					
						
							|  |  |  |             $nonce = $uri->param($nonce_name); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!$nonce) { | 
					
						
							|  |  |  |             $nonce = $uri->query($nonce_name); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!$nonce || !Utils::verifyNonce($nonce, $nonce_action)) { | 
					
						
							|  |  |  |             throw new PageExpiredException($this->getRequest()); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Return the best matching mime type for the request. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param  string[] $compare | 
					
						
							|  |  |  |      * @return string|null | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getAccept(array $compare): ?string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $accepted = []; | 
					
						
							|  |  |  |         foreach ($this->getRequest()->getHeader('Accept') as $accept) { | 
					
						
							|  |  |  |             foreach (explode(',', $accept) as $item) { | 
					
						
							|  |  |  |                 if (!$item) { | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 $split = explode(';q=', $item); | 
					
						
							|  |  |  |                 $mime = array_shift($split); | 
					
						
							|  |  |  |                 $priority = array_shift($split) ?? 1.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 $accepted[$mime] = $priority; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         arsort($accepted); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // TODO: add support for image/* etc
 | 
					
						
							|  |  |  |         $list = array_intersect($compare, array_keys($accepted)); | 
					
						
							|  |  |  |         if (!$list && (isset($accepted['*/*']) || isset($accepted['*']))) { | 
					
						
							|  |  |  |             return reset($compare) ?: null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return reset($list) ?: null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string $template | 
					
						
							|  |  |  |      * @return PageInterface | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function createPage(string $template): PageInterface | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $page = new Page(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Plugins may not have the correct Cache-Control header set, force no-store for the proxies.
 | 
					
						
							|  |  |  |         $page->expires(0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $filename = "plugin://admin/pages/admin/{$template}.md"; | 
					
						
							|  |  |  |         if (!file_exists($filename)) { | 
					
						
							|  |  |  |             throw new \RuntimeException(sprintf('Creating admin page %s failed: not found', $template)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Admin::DEBUG && Admin::addDebugMessage("Admin page: {$template}"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $page->init(new \SplFileInfo($filename)); | 
					
						
							|  |  |  |         $page->slug($template); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $page; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param string|null $url | 
					
						
							|  |  |  |      * @param int|null $code | 
					
						
							|  |  |  |      * @return ResponseInterface | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function createRedirectResponse(string $url = null, int $code = null): ResponseInterface | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $request = $this->getRequest(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (null === $url || '' === $url) { | 
					
						
							|  |  |  |             $url = (string)$request->getUri(); | 
					
						
							|  |  |  |         } elseif (mb_strpos($url, '/') === 0) { | 
					
						
							|  |  |  |             $url = $this->getAbsoluteAdminUrl($url); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (null === $code) { | 
					
						
							|  |  |  |             if (in_array($request->getMethod(), ['GET', 'HEAD'])) { | 
					
						
							|  |  |  |                 $code = 302; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 $code = 303; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $this->traitCreateRedirectResponse($url, $code); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param \Throwable $e | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getErrorJson(\Throwable $e): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $json = $this->traitGetErrorJson($e); | 
					
						
							|  |  |  |         $code = $e->getCode(); | 
					
						
							|  |  |  |         if ($code === 401) { | 
					
						
							|  |  |  |             $json['redirect'] = $this->getAbsoluteAdminUrl('/'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $json; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |