FlexDirectoryForm.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. <?php
  2. /**
  3. * @package Grav\Framework\Flex
  4. *
  5. * @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Framework\Flex;
  9. use ArrayAccess;
  10. use Exception;
  11. use Grav\Common\Data\Blueprint;
  12. use Grav\Common\Data\Data;
  13. use Grav\Common\Grav;
  14. use Grav\Common\Twig\Twig;
  15. use Grav\Common\Utils;
  16. use Grav\Framework\Flex\Interfaces\FlexDirectoryFormInterface;
  17. use Grav\Framework\Flex\Interfaces\FlexFormInterface;
  18. use Grav\Framework\Form\Interfaces\FormFlashInterface;
  19. use Grav\Framework\Form\Traits\FormTrait;
  20. use Grav\Framework\Route\Route;
  21. use JsonSerializable;
  22. use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
  23. use RuntimeException;
  24. use Twig\Error\LoaderError;
  25. use Twig\Error\SyntaxError;
  26. use Twig\Template;
  27. use Twig\TemplateWrapper;
  28. /**
  29. * Class FlexForm
  30. * @package Grav\Framework\Flex
  31. */
  32. class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
  33. {
  34. use NestedArrayAccessWithGetters {
  35. NestedArrayAccessWithGetters::get as private traitGet;
  36. NestedArrayAccessWithGetters::set as private traitSet;
  37. }
  38. use FormTrait {
  39. FormTrait::doSerialize as doTraitSerialize;
  40. FormTrait::doUnserialize as doTraitUnserialize;
  41. }
  42. /** @var array|null */
  43. private $form;
  44. /** @var FlexDirectory */
  45. private $directory;
  46. /** @var string */
  47. private $flexName;
  48. /**
  49. * @param array $options Options to initialize the form instance:
  50. * (string) name: Form name, allows you to use custom form.
  51. * (string) unique_id: Unique id for this form instance.
  52. * (array) form: Custom form fields.
  53. * (FlexDirectory) directory: Flex Directory, mandatory.
  54. *
  55. * @return FlexFormInterface
  56. */
  57. public static function instance(array $options = []): FlexFormInterface
  58. {
  59. if (isset($options['directory'])) {
  60. $directory = $options['directory'];
  61. if (!$directory instanceof FlexDirectory) {
  62. throw new RuntimeException(__METHOD__ . "(): 'directory' should be instance of FlexDirectory", 400);
  63. }
  64. unset($options['directory']);
  65. } else {
  66. throw new RuntimeException(__METHOD__ . "(): You need to pass option 'directory'", 400);
  67. }
  68. $name = $options['name'] ?? '';
  69. return $directory->getDirectoryForm($name, $options);
  70. }
  71. /**
  72. * FlexForm constructor.
  73. * @param string $name
  74. * @param FlexDirectory $directory
  75. * @param array|null $options
  76. */
  77. public function __construct(string $name, FlexDirectory $directory, array $options = null)
  78. {
  79. $this->name = $name;
  80. $this->setDirectory($directory);
  81. $this->setName($directory->getFlexType(), $name);
  82. $this->setId($this->getName());
  83. $uniqueId = $options['unique_id'] ?? null;
  84. if (!$uniqueId) {
  85. $uniqueId = md5($directory->getFlexType() . '-directory-' . $this->name);
  86. }
  87. $this->setUniqueId($uniqueId);
  88. $this->setFlashLookupFolder($directory->getDirectoryBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
  89. $this->form = $options['form'] ?? null;
  90. if (Utils::isPositive($this->form['disabled'] ?? false)) {
  91. $this->disable();
  92. }
  93. $this->initialize();
  94. }
  95. /**
  96. * @return $this
  97. */
  98. public function initialize()
  99. {
  100. $this->messages = [];
  101. $this->submitted = false;
  102. $this->data = new Data($this->directory->loadDirectoryConfig($this->name), $this->getBlueprint());
  103. $this->files = [];
  104. $this->unsetFlash();
  105. /** @var FlexFormFlash $flash */
  106. $flash = $this->getFlash();
  107. if ($flash->exists()) {
  108. $data = $flash->getData();
  109. $includeOriginal = (bool)($this->getBlueprint()->form()['images']['original'] ?? null);
  110. $directory = $flash->getDirectory();
  111. if (null === $directory) {
  112. throw new RuntimeException('Flash has no directory');
  113. }
  114. $this->directory = $directory;
  115. $this->data = $data ? new Data($data, $this->getBlueprint()) : null;
  116. $this->files = $flash->getFilesByFields($includeOriginal);
  117. }
  118. return $this;
  119. }
  120. /**
  121. * @param string $uniqueId
  122. * @return void
  123. */
  124. public function setUniqueId(string $uniqueId): void
  125. {
  126. if ($uniqueId !== '') {
  127. $this->uniqueid = $uniqueId;
  128. }
  129. }
  130. /**
  131. * @param string $name
  132. * @param mixed $default
  133. * @param string|null $separator
  134. * @return mixed
  135. */
  136. public function get($name, $default = null, $separator = null)
  137. {
  138. switch (strtolower($name)) {
  139. case 'id':
  140. case 'uniqueid':
  141. case 'name':
  142. case 'noncename':
  143. case 'nonceaction':
  144. case 'action':
  145. case 'data':
  146. case 'files':
  147. case 'errors';
  148. case 'fields':
  149. case 'blueprint':
  150. case 'page':
  151. $method = 'get' . $name;
  152. return $this->{$method}();
  153. }
  154. return $this->traitGet($name, $default, $separator);
  155. }
  156. /**
  157. * @param string $name
  158. * @param mixed $value
  159. * @param string|null $separator
  160. * @return $this
  161. */
  162. public function set($name, $value, $separator = null)
  163. {
  164. switch (strtolower($name)) {
  165. case 'id':
  166. case 'uniqueid':
  167. $method = 'set' . $name;
  168. return $this->{$method}();
  169. }
  170. return $this->traitSet($name, $value, $separator);
  171. }
  172. /**
  173. * @return string
  174. */
  175. public function getName(): string
  176. {
  177. return $this->flexName;
  178. }
  179. protected function setName(string $type, string $name): void
  180. {
  181. // Make sure that both type and name do not have dash (convert dashes to underscores).
  182. $type = str_replace('-', '_', $type);
  183. $name = str_replace('-', '_', $name);
  184. $this->flexName = $name ? "flex_conf-{$type}-{$name}" : "flex_conf-{$type}";
  185. }
  186. /**
  187. * @return Data|object
  188. */
  189. public function getData()
  190. {
  191. if (null === $this->data) {
  192. $this->data = new Data([], $this->getBlueprint());
  193. }
  194. return $this->data;
  195. }
  196. /**
  197. * Get a value from the form.
  198. *
  199. * Note: Used in form fields.
  200. *
  201. * @param string $name
  202. * @return mixed
  203. */
  204. public function getValue(string $name)
  205. {
  206. // Attempt to get value from the form data.
  207. $value = $this->data ? $this->data[$name] : null;
  208. // Return the form data or fall back to the object property.
  209. return $value ?? null;
  210. }
  211. /**
  212. * @param string $name
  213. * @return array|mixed|null
  214. */
  215. public function getDefaultValue(string $name)
  216. {
  217. return $this->getBlueprint()->getDefaultValue($name);
  218. }
  219. /**
  220. * @return array
  221. */
  222. public function getDefaultValues(): array
  223. {
  224. return $this->getBlueprint()->getDefaults();
  225. }
  226. /**
  227. * @return string
  228. */
  229. public function getFlexType(): string
  230. {
  231. return $this->directory->getFlexType();
  232. }
  233. /**
  234. * Get form flash object.
  235. *
  236. * @return FormFlashInterface|FlexFormFlash
  237. */
  238. public function getFlash()
  239. {
  240. if (null === $this->flash) {
  241. $grav = Grav::instance();
  242. $config = [
  243. 'session_id' => $this->getSessionId(),
  244. 'unique_id' => $this->getUniqueId(),
  245. 'form_name' => $this->getName(),
  246. 'folder' => $this->getFlashFolder(),
  247. 'directory' => $this->getDirectory()
  248. ];
  249. $this->flash = new FlexFormFlash($config);
  250. $this->flash
  251. ->setUrl($grav['uri']->url)
  252. ->setUser($grav['user'] ?? null);
  253. }
  254. return $this->flash;
  255. }
  256. /**
  257. * @return FlexDirectory
  258. */
  259. public function getDirectory(): FlexDirectory
  260. {
  261. return $this->directory;
  262. }
  263. /**
  264. * @return Blueprint
  265. */
  266. public function getBlueprint(): Blueprint
  267. {
  268. if (null === $this->blueprint) {
  269. try {
  270. $blueprint = $this->getDirectory()->getDirectoryBlueprint();
  271. if ($this->form) {
  272. // We have field overrides available.
  273. $blueprint->extend(['form' => $this->form], true);
  274. $blueprint->init();
  275. }
  276. } catch (RuntimeException $e) {
  277. if (!isset($this->form['fields'])) {
  278. throw $e;
  279. }
  280. // Blueprint is not defined, but we have custom form fields available.
  281. $blueprint = new Blueprint(null, ['form' => $this->form]);
  282. $blueprint->load();
  283. $blueprint->setScope('directory');
  284. $blueprint->init();
  285. }
  286. $this->blueprint = $blueprint;
  287. }
  288. return $this->blueprint;
  289. }
  290. /**
  291. * @return Route|null
  292. */
  293. public function getFileUploadAjaxRoute(): ?Route
  294. {
  295. return null;
  296. }
  297. /**
  298. * @param string|null $field
  299. * @param string|null $filename
  300. * @return Route|null
  301. */
  302. public function getFileDeleteAjaxRoute($field = null, $filename = null): ?Route
  303. {
  304. return null;
  305. }
  306. /**
  307. * @param array $params
  308. * @param string|null $extension
  309. * @return string
  310. */
  311. public function getMediaTaskRoute(array $params = [], string $extension = null): string
  312. {
  313. return '';
  314. }
  315. /**
  316. * @param string $name
  317. * @return mixed|null
  318. */
  319. public function __get($name)
  320. {
  321. $method = "get{$name}";
  322. if (method_exists($this, $method)) {
  323. return $this->{$method}();
  324. }
  325. $form = $this->getBlueprint()->form();
  326. return $form[$name] ?? null;
  327. }
  328. /**
  329. * @param string $name
  330. * @param mixed $value
  331. * @return void
  332. */
  333. public function __set($name, $value)
  334. {
  335. $method = "set{$name}";
  336. if (method_exists($this, $method)) {
  337. $this->{$method}($value);
  338. }
  339. }
  340. /**
  341. * @param string $name
  342. * @return bool
  343. */
  344. public function __isset($name)
  345. {
  346. $method = "get{$name}";
  347. if (method_exists($this, $method)) {
  348. return true;
  349. }
  350. $form = $this->getBlueprint()->form();
  351. return isset($form[$name]);
  352. }
  353. /**
  354. * @param string $name
  355. * @return void
  356. */
  357. public function __unset($name)
  358. {
  359. }
  360. /**
  361. * @return array|bool
  362. */
  363. protected function getUnserializeAllowedClasses()
  364. {
  365. return [FlexObject::class];
  366. }
  367. /**
  368. * Note: this method clones the object.
  369. *
  370. * @param FlexDirectory $directory
  371. * @return $this
  372. */
  373. protected function setDirectory(FlexDirectory $directory): self
  374. {
  375. $this->directory = $directory;
  376. return $this;
  377. }
  378. /**
  379. * @param string $layout
  380. * @return Template|TemplateWrapper
  381. * @throws LoaderError
  382. * @throws SyntaxError
  383. */
  384. protected function getTemplate($layout)
  385. {
  386. $grav = Grav::instance();
  387. /** @var Twig $twig */
  388. $twig = $grav['twig'];
  389. return $twig->twig()->resolveTemplate(
  390. [
  391. "flex-objects/layouts/{$this->getFlexType()}/form/{$layout}.html.twig",
  392. "flex-objects/layouts/_default/form/{$layout}.html.twig",
  393. "forms/{$layout}/form.html.twig",
  394. 'forms/default/form.html.twig'
  395. ]
  396. );
  397. }
  398. /**
  399. * @param array $data
  400. * @param array $files
  401. * @return void
  402. * @throws Exception
  403. */
  404. protected function doSubmit(array $data, array $files)
  405. {
  406. $this->directory->saveDirectoryConfig($this->name, $data);
  407. $this->reset();
  408. }
  409. /**
  410. * @return array
  411. */
  412. protected function doSerialize(): array
  413. {
  414. return $this->doTraitSerialize() + [
  415. 'form' => $this->form,
  416. 'directory' => $this->directory,
  417. 'flexName' => $this->flexName
  418. ];
  419. }
  420. /**
  421. * @param array $data
  422. * @return void
  423. */
  424. protected function doUnserialize(array $data): void
  425. {
  426. $this->doTraitUnserialize($data);
  427. $this->form = $data['form'];
  428. $this->directory = $data['directory'];
  429. $this->flexName = $data['flexName'];
  430. }
  431. /**
  432. * Filter validated data.
  433. *
  434. * @param ArrayAccess|Data|null $data
  435. */
  436. protected function filterData($data = null): void
  437. {
  438. if ($data instanceof Data) {
  439. $data->filter(false, true);
  440. }
  441. }
  442. }