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; } }