mirror of
				https://github.com/getgrav/grav-plugin-admin.git
				synced 2025-10-27 16:26:32 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			408 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			408 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| declare(strict_types=1);
 | |
| 
 | |
| namespace Grav\Plugin\Admin\Controllers;
 | |
| 
 | |
| use Grav\Common\Debugger;
 | |
| use Grav\Common\Grav;
 | |
| use Grav\Common\Inflector;
 | |
| use Grav\Common\Language\Language;
 | |
| use Grav\Common\Utils;
 | |
| use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
 | |
| use Grav\Framework\Form\Interfaces\FormInterface;
 | |
| use Grav\Framework\Psr7\Response;
 | |
| use Grav\Framework\RequestHandler\Exception\NotFoundException;
 | |
| use Grav\Framework\RequestHandler\Exception\PageExpiredException;
 | |
| use Grav\Framework\RequestHandler\Exception\RequestException;
 | |
| use Grav\Framework\Route\Route;
 | |
| use Grav\Framework\Session\SessionInterface;
 | |
| use Psr\Http\Message\ResponseInterface;
 | |
| use Psr\Http\Message\ServerRequestInterface;
 | |
| use Psr\Http\Server\RequestHandlerInterface;
 | |
| use RocketTheme\Toolbox\Event\Event;
 | |
| use RocketTheme\Toolbox\Session\Message;
 | |
| 
 | |
| abstract class AbstractController implements RequestHandlerInterface
 | |
| {
 | |
|     /** @var string */
 | |
|     protected $nonce_action = 'admin-form';
 | |
| 
 | |
|     /** @var string */
 | |
|     protected $nonce_name = 'admin-nonce';
 | |
| 
 | |
|     /** @var ServerRequestInterface */
 | |
|     protected $request;
 | |
| 
 | |
|     /** @var Grav */
 | |
|     protected $grav;
 | |
| 
 | |
|     /** @var string */
 | |
|     protected $type;
 | |
| 
 | |
|     /** @var string */
 | |
|     protected $key;
 | |
| 
 | |
|     /**
 | |
|      * Handle request.
 | |
|      *
 | |
|      * Fires event: admin.[directory].[task|action].[command]
 | |
|      *
 | |
|      * @param ServerRequestInterface $request
 | |
|      * @return Response
 | |
|      */
 | |
|     public function handle(ServerRequestInterface $request): ResponseInterface
 | |
|     {
 | |
|         $attributes = $request->getAttributes();
 | |
|         $this->request = $request;
 | |
|         $this->grav = $attributes['grav'] ?? Grav::instance();
 | |
|         $this->type =  $attributes['type'] ?? null;
 | |
|         $this->key =  $attributes['key'] ?? null;
 | |
| 
 | |
|         /** @var Route $route */
 | |
|         $route = $attributes['route'];
 | |
|         $post = $this->getPost();
 | |
| 
 | |
|         if ($this->isFormSubmit()) {
 | |
|             $form = $this->getForm();
 | |
|             $this->nonce_name = $attributes['nonce_name'] ?? $form->getNonceName();
 | |
|             $this->nonce_action = $attributes['nonce_action'] ?? $form->getNonceAction();
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $task = $request->getAttribute('task') ?? $post['task'] ?? $route->getParam('task');
 | |
|             if ($task) {
 | |
|                 if (empty($attributes['forwarded'])) {
 | |
|                     $this->checkNonce($task);
 | |
|                 }
 | |
|                 $type = 'task';
 | |
|                 $command = $task;
 | |
|             } else {
 | |
|                 $type = 'action';
 | |
|                 $command = $request->getAttribute('action') ?? $post['action'] ?? $route->getParam('action') ?? 'display';
 | |
|             }
 | |
|             $command = strtolower($command);
 | |
| 
 | |
|             $event = new Event(
 | |
|                 [
 | |
|                     'controller' => $this,
 | |
|                     'response' => null
 | |
|                 ]
 | |
|             );
 | |
| 
 | |
|             $this->grav->fireEvent("admin.{$this->type}.{$type}.{$command}", $event);
 | |
| 
 | |
|             $response = $event['response'];
 | |
|             if (!$response) {
 | |
|                 /** @var Inflector $inflector */
 | |
|                 $inflector = $this->grav['inflector'];
 | |
|                 $method = $type . $inflector::camelize($command);
 | |
|                 if ($method && method_exists($this, $method)) {
 | |
|                     $response = $this->{$method}($request);
 | |
|                 } else {
 | |
|                     throw new NotFoundException($request);
 | |
|                 }
 | |
|             }
 | |
|         } catch (\Exception $e) {
 | |
|             /** @var Debugger $debugger */
 | |
|             $debugger = $this->grav['debugger'];
 | |
|             $debugger->addException($e);
 | |
| 
 | |
|             $response = $this->createErrorResponse($e);
 | |
|         }
 | |
| 
 | |
|         if ($response instanceof Response) {
 | |
|             return $response;
 | |
|         }
 | |
| 
 | |
|         return $this->createJsonResponse($response);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get request.
 | |
|      *
 | |
|      * @return ServerRequestInterface
 | |
|      */
 | |
|     public function getRequest(): ServerRequestInterface
 | |
|     {
 | |
|         return $this->request;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string|null $name
 | |
|      * @param mixed $default
 | |
|      * @return mixed
 | |
|      */
 | |
|     public function getPost(string $name = null, $default = null)
 | |
|     {
 | |
|         $body = $this->request->getParsedBody();
 | |
| 
 | |
|         if ($name) {
 | |
|             return $body[$name] ?? $default;
 | |
|         }
 | |
| 
 | |
|         return $body;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if a form has been submitted.
 | |
|      *
 | |
|      * @return bool
 | |
|      */
 | |
|     public function isFormSubmit(): bool
 | |
|     {
 | |
|         return (bool)$this->getPost('__form-name__');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get form.
 | |
|      *
 | |
|      * @param string|null $type
 | |
|      * @return FormInterface
 | |
|      */
 | |
|     public function getForm(string $type = null): FormInterface
 | |
|     {
 | |
|         $object = $this->getObject();
 | |
|         if (!$object) {
 | |
|             throw new \RuntimeException('Not Found', 404);
 | |
|         }
 | |
| 
 | |
|         $formName = $this->getPost('__form-name__');
 | |
|         $uniqueId = $this->getPost('__unique_form_id__') ?: $formName;
 | |
| 
 | |
|         $form = $object->getForm($type ?? 'edit');
 | |
|         if ($uniqueId) {
 | |
|             $form->setUniqueId($uniqueId);
 | |
|         }
 | |
| 
 | |
|         return $form;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return FlexObjectInterface
 | |
|      */
 | |
|     abstract public function getObject();
 | |
| 
 | |
|     /**
 | |
|      * Get Grav instance.
 | |
|      *
 | |
|      * @return Grav
 | |
|      */
 | |
|     public function getGrav(): Grav
 | |
|     {
 | |
|         return $this->grav;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get session.
 | |
|      *
 | |
|      * @return SessionInterface
 | |
|      */
 | |
|     public function getSession(): SessionInterface
 | |
|     {
 | |
|         return $this->getGrav()['session'];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Display the current admin page.
 | |
|      *
 | |
|      * @return Response
 | |
|      */
 | |
|     public function createDisplayResponse(): ResponseInterface
 | |
|     {
 | |
|         return new Response(418);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create custom HTML response.
 | |
|      *
 | |
|      * @param string $content
 | |
|      * @param int $code
 | |
|      * @return Response
 | |
|      */
 | |
|     public function createHtmlResponse(string $content, int $code = null): ResponseInterface
 | |
|     {
 | |
|         return new Response($code ?: 200, [], $content);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create JSON response.
 | |
|      *
 | |
|      * @param array $content
 | |
|      * @return Response
 | |
|      */
 | |
|     public function createJsonResponse(array $content): ResponseInterface
 | |
|     {
 | |
|         $code = $content['code'] ?? 200;
 | |
|         if ($code >= 301 && $code <= 307) {
 | |
|             $code = 200;
 | |
|         }
 | |
| 
 | |
|         return new Response($code, ['Content-Type' => 'application/json'], json_encode($content));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create redirect response.
 | |
|      *
 | |
|      * @param string $url
 | |
|      * @param int $code
 | |
|      * @return Response
 | |
|      */
 | |
|     public function createRedirectResponse(string $url, int $code = null): ResponseInterface
 | |
|     {
 | |
|         if (null === $code || $code < 301 || $code > 307) {
 | |
|             $code = $this->grav['config']->get('system.pages.redirect_default_code', 302);
 | |
|         }
 | |
| 
 | |
|         $accept = $this->getAccept(['application/json', 'text/html']);
 | |
| 
 | |
|         if ($accept === 'application/json') {
 | |
|             return $this->createJsonResponse(['code' => $code, 'status' => 'redirect', 'redirect' => $url]);
 | |
|         }
 | |
| 
 | |
|         return new Response($code, ['Location' => $url]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create error response.
 | |
|      *
 | |
|      * @param  \Exception $exception
 | |
|      * @return Response
 | |
|      */
 | |
|     public function createErrorResponse(\Exception $exception): ResponseInterface
 | |
|     {
 | |
|         $validCodes = [
 | |
|             400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418,
 | |
|             422, 423, 424, 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 511
 | |
|         ];
 | |
| 
 | |
|         if ($exception instanceof RequestException) {
 | |
|             $code = $exception->getHttpCode();
 | |
|             $reason = $exception->getHttpReason();
 | |
|         } else {
 | |
|             $code = $exception->getCode();
 | |
|             $reason = null;
 | |
|         }
 | |
| 
 | |
|         if (!in_array($code, $validCodes, true)) {
 | |
|             $code = 500;
 | |
|         }
 | |
| 
 | |
|         $message = $exception->getMessage();
 | |
|         $response = [
 | |
|             'code' => $code,
 | |
|             'status' => 'error',
 | |
|             'message' => $message
 | |
|         ];
 | |
| 
 | |
|         $accept = $this->getAccept(['application/json', 'text/html']);
 | |
| 
 | |
|         if ($accept === 'text/html') {
 | |
|             $method = $this->getRequest()->getMethod();
 | |
| 
 | |
|             // On POST etc, redirect back to the previous page.
 | |
|             if ($method !== 'GET' && $method !== 'HEAD') {
 | |
|                 $this->setMessage($message, 'error');
 | |
|                 $referer = $this->request->getHeaderLine('Referer');
 | |
|                 return $this->createRedirectResponse($referer, 303);
 | |
|             }
 | |
| 
 | |
|             // TODO: improve error page
 | |
|             return $this->createHtmlResponse($response['message']);
 | |
|         }
 | |
| 
 | |
|         return new Response($code, ['Content-Type' => 'application/json'], json_encode($response), '1.1', $reason);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Translate a string.
 | |
|      *
 | |
|      * @param  string $string
 | |
|      * @return string
 | |
|      */
 | |
|     public function translate(string $string): string
 | |
|     {
 | |
|         /** @var Language $language */
 | |
|         $language = $this->grav['language'];
 | |
| 
 | |
|         return $language->translate($string);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set message to be shown in the admin.
 | |
|      *
 | |
|      * @param  string $message
 | |
|      * @param  string $type
 | |
|      * @return $this
 | |
|      */
 | |
|     public function setMessage($message, $type = 'info')
 | |
|     {
 | |
|         /** @var Message $messages */
 | |
|         $messages = $this->grav['messages'];
 | |
|         $messages->add($message, $type);
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if request nonce is valid.
 | |
|      *
 | |
|      * @param  string $task
 | |
|      * @throws PageExpiredException  If nonce is not valid.
 | |
|      */
 | |
|     protected function checkNonce(string $task): void
 | |
|     {
 | |
|         $nonce = null;
 | |
| 
 | |
|         if (\in_array(strtoupper($this->request->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
 | |
|             $nonce = $this->getPost($this->nonce_name);
 | |
|         }
 | |
| 
 | |
|         if (!$nonce) {
 | |
|             $nonce = $this->grav['uri']->param($this->nonce_name);
 | |
|         }
 | |
| 
 | |
|         if (!$nonce) {
 | |
|             $nonce = $this->grav['uri']->query($this->nonce_name);
 | |
|         }
 | |
| 
 | |
|         if (!$nonce || !Utils::verifyNonce($nonce, $this->nonce_action)) {
 | |
|             throw new PageExpiredException($this->request);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return the best matching mime type for the request.
 | |
|      *
 | |
|      * @param  string[] $compare
 | |
|      * @return string|null
 | |
|      */
 | |
|     protected function getAccept(array $compare): ?string
 | |
|     {
 | |
|         $accepted = [];
 | |
|         foreach ($this->request->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;
 | |
|     }
 | |
| }
 |