123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- <?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' => htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8')
- ];
- $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;
- }
- }
|