Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

AbstractController.php 11 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <?php
  2. /**
  3. * @package Grav\Plugin\Admin
  4. *
  5. * @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. declare(strict_types=1);
  9. namespace Grav\Plugin\Admin\Controllers;
  10. use Grav\Common\Debugger;
  11. use Grav\Common\Grav;
  12. use Grav\Common\Inflector;
  13. use Grav\Common\Language\Language;
  14. use Grav\Common\Utils;
  15. use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
  16. use Grav\Framework\Form\Interfaces\FormInterface;
  17. use Grav\Framework\Psr7\Response;
  18. use Grav\Framework\RequestHandler\Exception\NotFoundException;
  19. use Grav\Framework\RequestHandler\Exception\PageExpiredException;
  20. use Grav\Framework\RequestHandler\Exception\RequestException;
  21. use Grav\Framework\Route\Route;
  22. use Grav\Framework\Session\SessionInterface;
  23. use Psr\Http\Message\ResponseInterface;
  24. use Psr\Http\Message\ServerRequestInterface;
  25. use Psr\Http\Server\RequestHandlerInterface;
  26. use RocketTheme\Toolbox\Event\Event;
  27. use RocketTheme\Toolbox\Session\Message;
  28. abstract class AbstractController implements RequestHandlerInterface
  29. {
  30. /** @var string */
  31. protected $nonce_action = 'admin-form';
  32. /** @var string */
  33. protected $nonce_name = 'admin-nonce';
  34. /** @var ServerRequestInterface */
  35. protected $request;
  36. /** @var Grav */
  37. protected $grav;
  38. /** @var string */
  39. protected $type;
  40. /** @var string */
  41. protected $key;
  42. /**
  43. * Handle request.
  44. *
  45. * Fires event: admin.[directory].[task|action].[command]
  46. *
  47. * @param ServerRequestInterface $request
  48. * @return Response
  49. */
  50. public function handle(ServerRequestInterface $request): ResponseInterface
  51. {
  52. $attributes = $request->getAttributes();
  53. $this->request = $request;
  54. $this->grav = $attributes['grav'] ?? Grav::instance();
  55. $this->type = $attributes['type'] ?? null;
  56. $this->key = $attributes['key'] ?? null;
  57. /** @var Route $route */
  58. $route = $attributes['route'];
  59. $post = $this->getPost();
  60. if ($this->isFormSubmit()) {
  61. $form = $this->getForm();
  62. $this->nonce_name = $attributes['nonce_name'] ?? $form->getNonceName();
  63. $this->nonce_action = $attributes['nonce_action'] ?? $form->getNonceAction();
  64. }
  65. try {
  66. $task = $request->getAttribute('task') ?? $post['task'] ?? $route->getParam('task');
  67. if ($task) {
  68. if (empty($attributes['forwarded'])) {
  69. $this->checkNonce($task);
  70. }
  71. $type = 'task';
  72. $command = $task;
  73. } else {
  74. $type = 'action';
  75. $command = $request->getAttribute('action') ?? $post['action'] ?? $route->getParam('action') ?? 'display';
  76. }
  77. $command = strtolower($command);
  78. $event = new Event(
  79. [
  80. 'controller' => $this,
  81. 'response' => null
  82. ]
  83. );
  84. $this->grav->fireEvent("admin.{$this->type}.{$type}.{$command}", $event);
  85. $response = $event['response'];
  86. if (!$response) {
  87. /** @var Inflector $inflector */
  88. $inflector = $this->grav['inflector'];
  89. $method = $type . $inflector::camelize($command);
  90. if ($method && method_exists($this, $method)) {
  91. $response = $this->{$method}($request);
  92. } else {
  93. throw new NotFoundException($request);
  94. }
  95. }
  96. } catch (\Exception $e) {
  97. /** @var Debugger $debugger */
  98. $debugger = $this->grav['debugger'];
  99. $debugger->addException($e);
  100. $response = $this->createErrorResponse($e);
  101. }
  102. if ($response instanceof Response) {
  103. return $response;
  104. }
  105. return $this->createJsonResponse($response);
  106. }
  107. /**
  108. * Get request.
  109. *
  110. * @return ServerRequestInterface
  111. */
  112. public function getRequest(): ServerRequestInterface
  113. {
  114. return $this->request;
  115. }
  116. /**
  117. * @param string|null $name
  118. * @param mixed $default
  119. * @return mixed
  120. */
  121. public function getPost(string $name = null, $default = null)
  122. {
  123. $body = $this->request->getParsedBody();
  124. if ($name) {
  125. return $body[$name] ?? $default;
  126. }
  127. return $body;
  128. }
  129. /**
  130. * Check if a form has been submitted.
  131. *
  132. * @return bool
  133. */
  134. public function isFormSubmit(): bool
  135. {
  136. return (bool)$this->getPost('__form-name__');
  137. }
  138. /**
  139. * Get form.
  140. *
  141. * @param string|null $type
  142. * @return FormInterface
  143. */
  144. public function getForm(string $type = null): FormInterface
  145. {
  146. $object = $this->getObject();
  147. if (!$object) {
  148. throw new \RuntimeException('Not Found', 404);
  149. }
  150. $formName = $this->getPost('__form-name__');
  151. $uniqueId = $this->getPost('__unique_form_id__') ?: $formName;
  152. $form = $object->getForm($type ?? 'edit');
  153. if ($uniqueId) {
  154. $form->setUniqueId($uniqueId);
  155. }
  156. return $form;
  157. }
  158. /**
  159. * @return FlexObjectInterface
  160. */
  161. abstract public function getObject();
  162. /**
  163. * Get Grav instance.
  164. *
  165. * @return Grav
  166. */
  167. public function getGrav(): Grav
  168. {
  169. return $this->grav;
  170. }
  171. /**
  172. * Get session.
  173. *
  174. * @return SessionInterface
  175. */
  176. public function getSession(): SessionInterface
  177. {
  178. return $this->getGrav()['session'];
  179. }
  180. /**
  181. * Display the current admin page.
  182. *
  183. * @return Response
  184. */
  185. public function createDisplayResponse(): ResponseInterface
  186. {
  187. return new Response(418);
  188. }
  189. /**
  190. * Create custom HTML response.
  191. *
  192. * @param string $content
  193. * @param int $code
  194. * @return Response
  195. */
  196. public function createHtmlResponse(string $content, int $code = null): ResponseInterface
  197. {
  198. return new Response($code ?: 200, [], $content);
  199. }
  200. /**
  201. * Create JSON response.
  202. *
  203. * @param array $content
  204. * @return Response
  205. */
  206. public function createJsonResponse(array $content): ResponseInterface
  207. {
  208. $code = $content['code'] ?? 200;
  209. if ($code >= 301 && $code <= 307) {
  210. $code = 200;
  211. }
  212. return new Response($code, ['Content-Type' => 'application/json'], json_encode($content));
  213. }
  214. /**
  215. * Create redirect response.
  216. *
  217. * @param string $url
  218. * @param int $code
  219. * @return Response
  220. */
  221. public function createRedirectResponse(string $url, int $code = null): ResponseInterface
  222. {
  223. if (null === $code || $code < 301 || $code > 307) {
  224. $code = $this->grav['config']->get('system.pages.redirect_default_code', 302);
  225. }
  226. $accept = $this->getAccept(['application/json', 'text/html']);
  227. if ($accept === 'application/json') {
  228. return $this->createJsonResponse(['code' => $code, 'status' => 'redirect', 'redirect' => $url]);
  229. }
  230. return new Response($code, ['Location' => $url]);
  231. }
  232. /**
  233. * Create error response.
  234. *
  235. * @param \Exception $exception
  236. * @return Response
  237. */
  238. public function createErrorResponse(\Exception $exception): ResponseInterface
  239. {
  240. $validCodes = [
  241. 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418,
  242. 422, 423, 424, 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 511
  243. ];
  244. if ($exception instanceof RequestException) {
  245. $code = $exception->getHttpCode();
  246. $reason = $exception->getHttpReason();
  247. } else {
  248. $code = $exception->getCode();
  249. $reason = null;
  250. }
  251. if (!in_array($code, $validCodes, true)) {
  252. $code = 500;
  253. }
  254. $message = $exception->getMessage();
  255. $response = [
  256. 'code' => $code,
  257. 'status' => 'error',
  258. 'message' => htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8')
  259. ];
  260. $accept = $this->getAccept(['application/json', 'text/html']);
  261. if ($accept === 'text/html') {
  262. $method = $this->getRequest()->getMethod();
  263. // On POST etc, redirect back to the previous page.
  264. if ($method !== 'GET' && $method !== 'HEAD') {
  265. $this->setMessage($message, 'error');
  266. $referer = $this->request->getHeaderLine('Referer');
  267. return $this->createRedirectResponse($referer, 303);
  268. }
  269. // TODO: improve error page
  270. return $this->createHtmlResponse($response['message']);
  271. }
  272. return new Response($code, ['Content-Type' => 'application/json'], json_encode($response), '1.1', $reason);
  273. }
  274. /**
  275. * Translate a string.
  276. *
  277. * @param string $string
  278. * @return string
  279. */
  280. public function translate(string $string): string
  281. {
  282. /** @var Language $language */
  283. $language = $this->grav['language'];
  284. return $language->translate($string);
  285. }
  286. /**
  287. * Set message to be shown in the admin.
  288. *
  289. * @param string $message
  290. * @param string $type
  291. * @return $this
  292. */
  293. public function setMessage($message, $type = 'info')
  294. {
  295. /** @var Message $messages */
  296. $messages = $this->grav['messages'];
  297. $messages->add($message, $type);
  298. return $this;
  299. }
  300. /**
  301. * Check if request nonce is valid.
  302. *
  303. * @param string $task
  304. * @throws PageExpiredException If nonce is not valid.
  305. */
  306. protected function checkNonce(string $task): void
  307. {
  308. $nonce = null;
  309. if (\in_array(strtoupper($this->request->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
  310. $nonce = $this->getPost($this->nonce_name);
  311. }
  312. if (!$nonce) {
  313. $nonce = $this->grav['uri']->param($this->nonce_name);
  314. }
  315. if (!$nonce) {
  316. $nonce = $this->grav['uri']->query($this->nonce_name);
  317. }
  318. if (!$nonce || !Utils::verifyNonce($nonce, $this->nonce_action)) {
  319. throw new PageExpiredException($this->request);
  320. }
  321. }
  322. /**
  323. * Return the best matching mime type for the request.
  324. *
  325. * @param string[] $compare
  326. * @return string|null
  327. */
  328. protected function getAccept(array $compare): ?string
  329. {
  330. $accepted = [];
  331. foreach ($this->request->getHeader('Accept') as $accept) {
  332. foreach (explode(',', $accept) as $item) {
  333. if (!$item) {
  334. continue;
  335. }
  336. $split = explode(';q=', $item);
  337. $mime = array_shift($split);
  338. $priority = array_shift($split) ?? 1.0;
  339. $accepted[$mime] = $priority;
  340. }
  341. }
  342. arsort($accepted);
  343. // TODO: add support for image/* etc
  344. $list = array_intersect($compare, array_keys($accepted));
  345. if (!$list && (isset($accepted['*/*']) || isset($accepted['*']))) {
  346. return reset($compare) ?: null;
  347. }
  348. return reset($list) ?: null;
  349. }
  350. }