ObjectController.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. <?php
  2. declare(strict_types=1);
  3. namespace Grav\Plugin\FlexObjects\Controllers;
  4. use Grav\Common\Grav;
  5. use Grav\Framework\Flex\FlexForm;
  6. use Grav\Framework\Flex\FlexObject;
  7. use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
  8. use Grav\Framework\Route\Route;
  9. use Nyholm\Psr7\ServerRequest;
  10. use Psr\Http\Message\ResponseInterface;
  11. use Psr\Http\Message\ServerRequestInterface;
  12. use RocketTheme\Toolbox\Event\Event;
  13. use RuntimeException;
  14. /**
  15. * Object controller is for the frontend.
  16. *
  17. * Currently following tasks are supported:
  18. *
  19. * - save (create or update)
  20. * - create
  21. * - update
  22. * - delete
  23. * - reset
  24. * - preview
  25. */
  26. class ObjectController extends AbstractController
  27. {
  28. /**
  29. * Save object.
  30. *
  31. * Forwards call to either create or update task.
  32. *
  33. * @param ServerRequestInterface $request
  34. * @return ResponseInterface
  35. */
  36. public function taskSave(ServerRequestInterface $request): ResponseInterface
  37. {
  38. $form = $this->getForm();
  39. $object = $form->getObject();
  40. return $object->exists() ? $this->taskUpdate($request) : $this->taskCreate($request);
  41. }
  42. /**
  43. * Create object.
  44. *
  45. * Task fails if object exists.
  46. *
  47. * @param ServerRequestInterface $request
  48. * @return ResponseInterface
  49. */
  50. public function taskCreate(ServerRequestInterface $request): ResponseInterface
  51. {
  52. $this->checkAuthorization('create');
  53. $form = $this->getForm();
  54. $callable = function (array $data, array $files, FlexObject $object) {
  55. if (method_exists($object, 'storeOriginal')) {
  56. $object->storeOriginal();
  57. }
  58. $object->update($data, $files);
  59. if (\is_callable([$object, 'check'])) {
  60. $object->check($this->user);
  61. }
  62. $object->save();
  63. };
  64. $form->setSubmitMethod($callable);
  65. $form->handleRequest($request);
  66. if (!$form->isValid()) {
  67. $error = $form->getError();
  68. if ($error) {
  69. $this->setMessage($error, 'error');
  70. }
  71. $errors = $form->getErrors();
  72. foreach ($errors as $field) {
  73. foreach ($field as $error) {
  74. $this->setMessage($error, 'error');
  75. }
  76. }
  77. $data = $form->getData();
  78. if (null !== $data) {
  79. $object = $form->getObject();
  80. $flash = $form->getFlash();
  81. $flash->setObject($object);
  82. $flash->setData($data->toArray());
  83. $flash->save();
  84. }
  85. return $this->createDisplayResponse();
  86. }
  87. // FIXME: make it conditional
  88. $grav = $this->grav;
  89. $grav->fireEvent('gitsync');
  90. $this->object = $form->getObject();
  91. $event = new Event(
  92. [
  93. 'controller' => $this,
  94. 'object' => $this->object,
  95. 'response' => null,
  96. 'message' => null,
  97. ]
  98. );
  99. $this->grav->fireEvent("flex.{$this->type}.task.create.after", $event);
  100. $this->setMessage($event['message'] ?? $this->translate('PLUGIN_FLEX_OBJECTS.STATE.CREATED_SUCCESSFULLY'), 'info');
  101. if ($event['response']) {
  102. return $event['response'];
  103. }
  104. $redirect = $request->getAttribute('redirect', (string)$request->getUri());
  105. return $this->createRedirectResponse($redirect, 303);
  106. }
  107. /**
  108. * Update object.
  109. *
  110. * Task fails if object does not exist.
  111. *
  112. * @param ServerRequestInterface $request
  113. * @return ResponseInterface
  114. */
  115. public function taskUpdate(ServerRequestInterface $request): ResponseInterface
  116. {
  117. $this->checkAuthorization('update');
  118. $form = $this->getForm();
  119. $callable = function (array $data, array $files, FlexObject $object) {
  120. if (method_exists($object, 'storeOriginal')) {
  121. $object->storeOriginal();
  122. }
  123. $object->update($data, $files);
  124. if (\is_callable([$object, 'check'])) {
  125. $object->check($this->user);
  126. }
  127. $object->save();
  128. };
  129. $form->setSubmitMethod($callable);
  130. $form->handleRequest($request);
  131. if (!$form->isValid()) {
  132. $error = $form->getError();
  133. if ($error) {
  134. $this->setMessage($error, 'error');
  135. }
  136. $errors = $form->getErrors();
  137. foreach ($errors as $field) {
  138. foreach ($field as $error) {
  139. $this->setMessage($error, 'error');
  140. }
  141. }
  142. $data = $form->getData();
  143. if (null !== $data) {
  144. $object = $form->getObject();
  145. $flash = $form->getFlash();
  146. $flash->setObject($object);
  147. $flash->setData($data->toArray());
  148. $flash->save();
  149. }
  150. return $this->createDisplayResponse();
  151. }
  152. // FIXME: make it conditional
  153. $grav = $this->grav;
  154. $grav->fireEvent('gitsync');
  155. $this->object = $form->getObject();
  156. $event = new Event(
  157. [
  158. 'controller' => $this,
  159. 'object' => $this->object,
  160. 'response' => null,
  161. 'message' => null,
  162. ]
  163. );
  164. $this->grav->fireEvent("flex.{$this->type}.task.update.after", $event);
  165. $this->setMessage($event['message'] ?? $this->translate('PLUGIN_FLEX_OBJECTS.STATE.UPDATED_SUCCESSFULLY'), 'info');
  166. if ($event['response']) {
  167. return $event['response'];
  168. }
  169. $redirect = $request->getAttribute('redirect', (string)$request->getUri()->getPath());
  170. return $this->createRedirectResponse($redirect, 303);
  171. }
  172. /**
  173. * Delete object.
  174. *
  175. * @param ServerRequestInterface $request
  176. * @return ResponseInterface
  177. */
  178. public function taskDelete(ServerRequestInterface $request): ResponseInterface
  179. {
  180. $this->checkAuthorization('delete');
  181. $object = $this->getObject();
  182. if (!$object) {
  183. throw new RuntimeException('Not Found', 404);
  184. }
  185. $object->delete();
  186. // FIXME: make it conditional
  187. $grav = $this->grav;
  188. $grav->fireEvent('gitsync');
  189. $event = new Event(
  190. [
  191. 'controller' => $this,
  192. 'object' => $object,
  193. 'response' => null,
  194. 'message' => null,
  195. ]
  196. );
  197. $this->grav->fireEvent("flex.{$this->type}.task.delete.after", $event);
  198. $this->setMessage($this->translate($event['message'] ?? 'PLUGIN_FLEX_OBJECTS.STATE.DELETED_SUCCESSFULLY'), 'info');
  199. if ($event['response']) {
  200. return $event['response'];
  201. }
  202. $redirect = $request->getAttribute('redirect', (string)$request->getUri()->getPath());
  203. return $this->createRedirectResponse($redirect, 303);
  204. }
  205. /**
  206. * Reset form to original values.
  207. *
  208. * @param ServerRequestInterface $request
  209. * @return ResponseInterface
  210. */
  211. public function taskReset(ServerRequestInterface $request): ResponseInterface
  212. {
  213. $this->checkAuthorization('save');
  214. $flash = $this->getForm()->getFlash();
  215. $flash->delete();
  216. $redirect = $request->getAttribute('redirect', (string)$request->getUri()->getPath());
  217. return $this->createRedirectResponse($redirect, 303);
  218. }
  219. /**
  220. * Preview object.
  221. *
  222. * Takes a form input and converts it to visible presentation of the object.
  223. *
  224. * @param ServerRequestInterface $request
  225. * @return ResponseInterface
  226. */
  227. public function taskPreview(ServerRequestInterface $request): ResponseInterface
  228. {
  229. $this->checkAuthorization('save');
  230. /** @var FlexForm $form */
  231. $form = $this->getForm('edit');
  232. $form->setRequest($request);
  233. if (!$form->validate()) {
  234. $error = $form->getError();
  235. if ($error) {
  236. $this->setMessage($error, 'error');
  237. }
  238. $errors = $form->getErrors();
  239. foreach ($errors as $field) {
  240. foreach ($field as $error) {
  241. $this->setMessage($error, 'error');
  242. }
  243. }
  244. return $this->createRedirectResponse((string)$request->getUri(), 303);
  245. }
  246. $this->object = $form->updateObject();
  247. return $this->actionDisplayPreview();
  248. }
  249. /**
  250. * @param ServerRequestInterface $request
  251. * @return ResponseInterface
  252. */
  253. public function taskMediaList(ServerRequestInterface $request): ResponseInterface
  254. {
  255. $directory = $this->getDirectory();
  256. if (!$directory) {
  257. throw new RuntimeException('Not Found', 404);
  258. }
  259. return $this->forwardMediaTask('action', 'media.list');
  260. }
  261. /**
  262. * @param ServerRequestInterface $request
  263. * @return ResponseInterface
  264. */
  265. public function taskMediaUpload(ServerRequestInterface $request): ResponseInterface
  266. {
  267. $directory = $this->getDirectory();
  268. if (!$directory) {
  269. throw new RuntimeException('Not Found', 404);
  270. }
  271. return $this->forwardMediaTask('task', 'media.upload');
  272. }
  273. /**
  274. * @param ServerRequestInterface $request
  275. * @return ResponseInterface
  276. */
  277. public function taskMediaDelete(ServerRequestInterface $request): ResponseInterface
  278. {
  279. $directory = $this->getDirectory();
  280. if (!$directory) {
  281. throw new RuntimeException('Not Found', 404);
  282. }
  283. return $this->forwardMediaTask('task', 'media.delete');
  284. }
  285. /**
  286. * @param ServerRequestInterface $request
  287. * @return ResponseInterface
  288. */
  289. public function taskGetFilesInFolder(ServerRequestInterface $request): ResponseInterface
  290. {
  291. $directory = $this->getDirectory();
  292. if (!$directory) {
  293. throw new RuntimeException('Not Found', 404);
  294. }
  295. return $this->forwardMediaTask('action', 'media.picker');
  296. }
  297. /**
  298. * @param ServerRequestInterface $request
  299. * @return ResponseInterface
  300. * @deprecated Do not use
  301. */
  302. public function taskFilesUpload(ServerRequestInterface $request): ResponseInterface
  303. {
  304. /** @var Route $route */
  305. $route = $this->grav['route'];
  306. if ($route->getParam('task') === 'media.upload') {
  307. return $this->taskMediaUpload($request);
  308. }
  309. throw new RuntimeException('Task filesUpload should not be called, please update form plugin!', 400);
  310. }
  311. /**
  312. * @param ServerRequestInterface $request
  313. * @return ResponseInterface
  314. * @deprecated Do not use
  315. */
  316. public function taskRemoveMedia(ServerRequestInterface $request): ResponseInterface
  317. {
  318. /** @var Route $route */
  319. $route = $this->grav['route'];
  320. if ($route->getParam('task') === 'media.delete') {
  321. return $this->taskMediaDelete($request);
  322. }
  323. throw new RuntimeException('Task removeMedia should not be called, please update form plugin!', 400);
  324. }
  325. /**
  326. * Display object preview.
  327. *
  328. * @return ResponseInterface
  329. */
  330. public function actionDisplayPreview(): ResponseInterface
  331. {
  332. $this->checkAuthorization('save');
  333. $this->checkAuthorization('read');
  334. $object = $this->getObject();
  335. if (!$object) {
  336. throw new RuntimeException('No object found!', 404);
  337. }
  338. $grav = Grav::instance();
  339. $grav['twig']->init();
  340. $grav['theme'];
  341. $content = [
  342. 'code' => 200,
  343. 'id' => $object->getKey(),
  344. 'exists' => $object->exists(),
  345. 'html' => (string)$object->render('preview', ['nocache' => []])
  346. ];
  347. $accept = $this->getAccept(['application/json', 'text/html']);
  348. if ($accept === 'text/html') {
  349. return $this->createHtmlResponse($content['html']);
  350. }
  351. if ($accept === 'application/json') {
  352. return $this->createJsonResponse($content);
  353. }
  354. throw new RuntimeException('Not found', 404);
  355. }
  356. /**
  357. * @param string $action
  358. * @return void
  359. * @throws RuntimeException
  360. */
  361. public function checkAuthorization(string $action): void
  362. {
  363. $object = $this->getObject();
  364. if (!$object) {
  365. throw new RuntimeException('Not Found', 404);
  366. }
  367. if ($object instanceof FlexAuthorizeInterface) {
  368. if (!$object->isAuthorized($action, null, $this->user)) {
  369. throw new RuntimeException('Forbidden', 403);
  370. }
  371. }
  372. }
  373. /**
  374. * @param string[] $actions
  375. * @return void
  376. * @throws RuntimeException
  377. */
  378. public function checkAuthorizations(array $actions): void
  379. {
  380. $object = $this->getObject();
  381. if (!$object) {
  382. throw new RuntimeException('Not Found', 404);
  383. }
  384. if ($object instanceof FlexAuthorizeInterface) {
  385. $test = false;
  386. foreach ($actions as $action) {
  387. $test |= $object->isAuthorized($action, null, $this->user);
  388. }
  389. if (!$test) {
  390. throw new RuntimeException('Forbidden', 403);
  391. }
  392. }
  393. }
  394. /**
  395. * @param string $type
  396. * @param string $name
  397. * @return ResponseInterface
  398. */
  399. protected function forwardMediaTask(string $type, string $name): ResponseInterface
  400. {
  401. /** @var Route $route */
  402. $route = $this->grav['route']->withGravParam('task', null)->withGravParam($type, $name);
  403. $object = $this->getObject();
  404. /** @var ServerRequest $request */
  405. $request = $this->grav['request'];
  406. $request = $request
  407. ->withAttribute($type, $name)
  408. ->withAttribute('type', $this->type)
  409. ->withAttribute('key', $this->key)
  410. ->withAttribute('storage_key', $object && $object->exists() ? $object->getStorageKey() : null)
  411. ->withAttribute('route', $route)
  412. ->withAttribute('forwarded', true)
  413. ->withAttribute('object', $object);
  414. $controller = new MediaController();
  415. if ($this->user) {
  416. $controller->setUser($this->user);
  417. }
  418. return $controller->handle($request);
  419. }
  420. }