ObjectController.php 16 KB

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