PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/web/modules/contrib/ctools/src/Wizard/FormWizardBase.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 471 lines | 273 code | 48 blank | 150 comment | 24 complexity | b0726ac0b7f1ab64f87925c1fb0cda21 MD5 | raw file
  1. <?php
  2. namespace Drupal\ctools\Wizard;
  3. use Drupal\Core\Ajax\AjaxResponse;
  4. use Drupal\Core\Ajax\CloseModalDialogCommand;
  5. use Drupal\Core\DependencyInjection\ClassResolverInterface;
  6. use Drupal\Core\Form\FormBase;
  7. use Drupal\Core\Form\FormBuilderInterface;
  8. use Drupal\Core\Form\FormInterface;
  9. use Drupal\Core\Form\FormStateInterface;
  10. use Drupal\Core\Routing\RouteMatchInterface;
  11. use Drupal\Core\Url;
  12. use Drupal\ctools\Ajax\OpenModalWizardCommand;
  13. use Drupal\ctools\Event\WizardEvent;
  14. use Drupal\Core\TempStore\SharedTempStoreFactory;
  15. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  16. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  17. /**
  18. * The base class for all form wizard.
  19. */
  20. abstract class FormWizardBase extends FormBase implements FormWizardInterface {
  21. /**
  22. * Tempstore Factory for keeping track of values in each step of the wizard.
  23. *
  24. * @var \Drupal\Core\TempStore\SharedTempStoreFactory
  25. */
  26. protected $tempstore;
  27. /**
  28. * The Form Builder.
  29. *
  30. * @var \Drupal\Core\Form\FormBuilderInterface
  31. */
  32. protected $builder;
  33. /**
  34. * The class resolver.
  35. *
  36. * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
  37. */
  38. protected $classResolver;
  39. /**
  40. * The event dispatcher.
  41. *
  42. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  43. */
  44. protected $dispatcher;
  45. /**
  46. * The shared temp store factory collection name.
  47. *
  48. * @var string
  49. */
  50. protected $tempstore_id;
  51. /**
  52. * The SharedTempStore key for our current wizard values.
  53. *
  54. * @var string|null
  55. */
  56. protected $machine_name;
  57. /**
  58. * The current active step of the wizard.
  59. *
  60. * @var string|null
  61. */
  62. protected $step;
  63. /**
  64. * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore
  65. * Tempstore Factory for keeping track of values in each step of the
  66. * wizard.
  67. * @param \Drupal\Core\Form\FormBuilderInterface $builder
  68. * The Form Builder.
  69. * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
  70. * The class resolver.
  71. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
  72. * The event dispatcher.
  73. * @param $tempstore_id
  74. * The shared temp store factory collection name.
  75. * @param null $machine_name
  76. * The SharedTempStore key for our current wizard values.
  77. * @param null $step
  78. * The current active step of the wizard.
  79. */
  80. public function __construct(SharedTempStoreFactory $tempstore, FormBuilderInterface $builder, ClassResolverInterface $class_resolver, EventDispatcherInterface $event_dispatcher, RouteMatchInterface $route_match, $tempstore_id, $machine_name = NULL, $step = NULL) {
  81. $this->tempstore = $tempstore;
  82. $this->builder = $builder;
  83. $this->classResolver = $class_resolver;
  84. $this->dispatcher = $event_dispatcher;
  85. $this->routeMatch = $route_match;
  86. $this->tempstore_id = $tempstore_id;
  87. $this->machine_name = $machine_name;
  88. $this->step = $step;
  89. }
  90. /**
  91. * {@inheritdoc}
  92. */
  93. public static function getParameters() {
  94. return [
  95. 'tempstore' => \Drupal::service('tempstore.shared'),
  96. 'builder' => \Drupal::service('form_builder'),
  97. 'class_resolver' => \Drupal::service('class_resolver'),
  98. 'event_dispatcher' => \Drupal::service('event_dispatcher'),
  99. ];
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function initValues() {
  105. $values = [];
  106. $event = new WizardEvent($this, $values);
  107. $this->dispatcher->dispatch(FormWizardInterface::LOAD_VALUES, $event);
  108. return $event->getValues();
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. public function getTempstoreId() {
  114. return $this->tempstore_id;
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function getTempstore() {
  120. $tempstore = $this->tempstore->get($this->getTempstoreId());
  121. return $tempstore;
  122. }
  123. /**
  124. * {@inheritdoc}
  125. */
  126. public function getMachineName() {
  127. return $this->machine_name;
  128. }
  129. /**
  130. * {@inheritdoc}
  131. */
  132. public function getStep($cached_values) {
  133. if (!$this->step) {
  134. $operations = $this->getOperations($cached_values);
  135. $steps = array_keys($operations);
  136. $this->step = reset($steps);
  137. }
  138. return $this->step;
  139. }
  140. /**
  141. * {@inheritdoc}
  142. */
  143. public function getOperation($cached_values) {
  144. $operations = $this->getOperations($cached_values);
  145. $step = $this->getStep($cached_values);
  146. if (!empty($operations[$step])) {
  147. return $operations[$step];
  148. }
  149. throw new NotFoundHttpException();
  150. }
  151. /**
  152. * The translated text of the "Next" button's text.
  153. *
  154. * @return string
  155. */
  156. public function getNextOp() {
  157. return $this->t('Next');
  158. }
  159. /**
  160. * {@inheritdoc}
  161. */
  162. public function getNextParameters($cached_values) {
  163. // Get the steps by key.
  164. $operations = $this->getOperations($cached_values);
  165. $steps = array_keys($operations);
  166. // Get the steps after the current step.
  167. $after = array_slice($operations, array_search($this->getStep($cached_values), $steps) + 1);
  168. // Get the steps after the current step by key.
  169. $after_keys = array_keys($after);
  170. $step = reset($after_keys);
  171. if (!$step) {
  172. $keys = array_keys($operations);
  173. $step = end($keys);
  174. }
  175. return [
  176. 'machine_name' => $this->getMachineName(),
  177. 'step' => $step,
  178. 'js' => 'nojs',
  179. ];
  180. }
  181. /**
  182. * {@inheritdoc}
  183. */
  184. public function getPreviousParameters($cached_values) {
  185. $operations = $this->getOperations($cached_values);
  186. $step = $this->getStep($cached_values);
  187. // Get the steps by key.
  188. $steps = array_keys($operations);
  189. // Get the steps before the current step.
  190. $before = array_slice($operations, 0, array_search($step, $steps));
  191. // Get the steps before the current step by key.
  192. $before = array_keys($before);
  193. // Reverse the steps for easy access to the next step.
  194. $before_steps = array_reverse($before);
  195. $step = reset($before_steps);
  196. return [
  197. 'machine_name' => $this->getMachineName(),
  198. 'step' => $step,
  199. 'js' => 'nojs',
  200. ];
  201. }
  202. /**
  203. * {@inheritdoc}
  204. */
  205. public function getFormId() {
  206. if (!$this->getMachineName() || !$this->getTempstore()->get($this->getMachineName())) {
  207. $cached_values = $this->initValues();
  208. }
  209. else {
  210. $cached_values = $this->getTempstore()->get($this->getMachineName());
  211. }
  212. $operation = $this->getOperation($cached_values);
  213. /* @var $operation \Drupal\Core\Form\FormInterface */
  214. $operation = $this->classResolver->getInstanceFromDefinition($operation['form']);
  215. return $operation->getFormId();
  216. }
  217. /**
  218. * {@inheritdoc}
  219. */
  220. public function buildForm(array $form, FormStateInterface $form_state) {
  221. $cached_values = $form_state->getTemporaryValue('wizard');
  222. // Get the current form operation.
  223. $operation = $this->getOperation($cached_values);
  224. $form = $this->customizeForm($form, $form_state);
  225. /* @var $formClass \Drupal\Core\Form\FormInterface */
  226. $formClass = $this->classResolver->getInstanceFromDefinition($operation['form']);
  227. // Pass include any custom values for this operation.
  228. if (!empty($operation['values'])) {
  229. $cached_values = array_merge($cached_values, $operation['values']);
  230. $form_state->setTemporaryValue('wizard', $cached_values);
  231. }
  232. // Build the form.
  233. $form = $formClass->buildForm($form, $form_state);
  234. if (isset($operation['title'])) {
  235. $form['#title'] = $operation['title'];
  236. }
  237. $form['actions'] = $this->actions($formClass, $form_state);
  238. return $form;
  239. }
  240. /**
  241. * {@inheritdoc}
  242. */
  243. public function validateForm(array &$form, FormStateInterface $form_state) {}
  244. /**
  245. * {@inheritdoc}
  246. */
  247. public function submitForm(array &$form, FormStateInterface $form_state) {
  248. // Only perform this logic if we're moving to the next page. This prevents
  249. // the loss of cached values on ajax submissions.
  250. if ((string) $form_state->getValue('op') == (string) $this->getNextOp()) {
  251. $cached_values = $form_state->getTemporaryValue('wizard');
  252. if ($form_state->hasValue('label')) {
  253. $cached_values['label'] = $form_state->getValue('label');
  254. }
  255. if ($form_state->hasValue('id')) {
  256. $cached_values['id'] = $form_state->getValue('id');
  257. }
  258. if (is_null($this->machine_name) && !empty($cached_values['id'])) {
  259. $this->machine_name = $cached_values['id'];
  260. }
  261. $this->getTempstore()->set($this->getMachineName(), $cached_values);
  262. if (!$form_state->get('ajax')) {
  263. $form_state->setRedirect($this->getRouteName(), $this->getNextParameters($cached_values));
  264. }
  265. }
  266. }
  267. /**
  268. * {@inheritdoc}
  269. */
  270. public function populateCachedValues(array &$form, FormStateInterface $form_state) {
  271. $cached_values = $this->getTempstore()->get($this->getMachineName());
  272. if (!$cached_values) {
  273. $cached_values = $form_state->getTemporaryValue('wizard');
  274. if (!$cached_values) {
  275. $cached_values = $this->initValues();
  276. $form_state->setTemporaryValue('wizard', $cached_values);
  277. }
  278. }
  279. }
  280. /**
  281. * {@inheritdoc}
  282. */
  283. public function previous(array &$form, FormStateInterface $form_state) {
  284. $cached_values = $form_state->getTemporaryValue('wizard');
  285. $form_state->setRedirect($this->getRouteName(), $this->getPreviousParameters($cached_values));
  286. }
  287. /**
  288. * {@inheritdoc}
  289. */
  290. public function finish(array &$form, FormStateInterface $form_state) {
  291. $this->getTempstore()->delete($this->getMachineName());
  292. }
  293. /**
  294. * Helper function for generating default form elements.
  295. *
  296. * @param array $form
  297. * @param \Drupal\Core\Form\FormStateInterface $form_state
  298. *
  299. * @return array
  300. */
  301. protected function customizeForm(array $form, FormStateInterface $form_state) {
  302. // Setup the step rendering theme element.
  303. $prefix = [
  304. '#theme' => ['ctools_wizard_trail'],
  305. '#wizard' => $this,
  306. '#cached_values' => $form_state->getTemporaryValue('wizard'),
  307. ];
  308. // @todo properly inject the renderer.
  309. $form['#prefix'] = \Drupal::service('renderer')->render($prefix);
  310. return $form;
  311. }
  312. /**
  313. * Generates action elements for navigating between the operation steps.
  314. *
  315. * @param \Drupal\Core\Form\FormInterface $form_object
  316. * The current operation form.
  317. * @param \Drupal\Core\Form\FormStateInterface $form_state
  318. * The current form state.
  319. *
  320. * @return array
  321. */
  322. protected function actions(FormInterface $form_object, FormStateInterface $form_state) {
  323. $cached_values = $form_state->getTemporaryValue('wizard');
  324. $operations = $this->getOperations($cached_values);
  325. $step = $this->getStep($cached_values);
  326. $operation = $operations[$step];
  327. $steps = array_keys($operations);
  328. // Slice to find the operations that occur before the current operation.
  329. $before = array_slice($operations, 0, array_search($step, $steps));
  330. // Slice to find the operations that occur after the current operation.
  331. $after = array_slice($operations, array_search($step, $steps) + 1);
  332. $actions = [
  333. 'submit' => [
  334. '#type' => 'submit',
  335. '#value' => $this->t('Next'),
  336. '#button_type' => 'primary',
  337. '#validate' => [
  338. '::populateCachedValues',
  339. [$form_object, 'validateForm'],
  340. ],
  341. '#submit' => [
  342. [$form_object, 'submitForm'],
  343. ],
  344. ],
  345. ];
  346. // Add any submit or validate functions for the step and the global ones.
  347. if (isset($operation['validate'])) {
  348. $actions['submit']['#validate'] = array_merge($actions['submit']['#validate'], $operation['validate']);
  349. }
  350. $actions['submit']['#validate'][] = '::validateForm';
  351. if (isset($operation['submit'])) {
  352. $actions['submit']['#submit'] = array_merge($actions['submit']['#submit'], $operation['submit']);
  353. }
  354. $actions['submit']['#submit'][] = '::submitForm';
  355. if ($form_state->get('ajax')) {
  356. // Ajax submissions need to submit to the current step, not "next".
  357. $parameters = $this->getNextParameters($cached_values);
  358. $parameters['step'] = $this->getStep($cached_values);
  359. $actions['submit']['#ajax'] = [
  360. 'callback' => '::ajaxSubmit',
  361. 'url' => Url::fromRoute($this->getRouteName(), $parameters),
  362. 'options' => ['query' => $this->getRequest()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
  363. ];
  364. }
  365. // If there are steps before this one, label the button "previous"
  366. // otherwise do not display a button.
  367. if ($before) {
  368. $actions['previous'] = [
  369. '#type' => 'submit',
  370. '#value' => $this->t('Previous'),
  371. '#validate' => [
  372. [$this, 'populateCachedValues'],
  373. ],
  374. '#submit' => [
  375. [$this, 'previous'],
  376. ],
  377. '#limit_validation_errors' => [],
  378. '#weight' => -10,
  379. ];
  380. if ($form_state->get('ajax')) {
  381. // Ajax submissions need to submit to the current step, not "previous".
  382. $parameters = $this->getPreviousParameters($cached_values);
  383. $parameters['step'] = $this->getStep($cached_values);
  384. $actions['previous']['#ajax'] = [
  385. 'callback' => '::ajaxPrevious',
  386. 'url' => Url::fromRoute($this->getRouteName(), $parameters),
  387. 'options' => ['query' => $this->getRequest()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
  388. ];
  389. }
  390. }
  391. // If there are not steps after this one, label the button "Finish".
  392. if (!$after) {
  393. $actions['submit']['#value'] = $this->t('Finish');
  394. $actions['submit']['#submit'][] = [$this, 'finish'];
  395. if ($form_state->get('ajax')) {
  396. $actions['submit']['#ajax']['callback'] = [$this, 'ajaxFinish'];
  397. }
  398. }
  399. return $actions;
  400. }
  401. public function ajaxSubmit(array $form, FormStateInterface $form_state) {
  402. $cached_values = $form_state->getTemporaryValue('wizard');
  403. $response = new AjaxResponse();
  404. $parameters = $this->getNextParameters($cached_values);
  405. $response->addCommand(new OpenModalWizardCommand($this, $this->getTempstoreId(), $parameters));
  406. return $response;
  407. }
  408. public function ajaxPrevious(array $form, FormStateInterface $form_state) {
  409. $cached_values = $form_state->getTemporaryValue('wizard');
  410. $response = new AjaxResponse();
  411. $parameters = $this->getPreviousParameters($cached_values);
  412. $response->addCommand(new OpenModalWizardCommand($this, $this->getTempstoreId(), $parameters));
  413. return $response;
  414. }
  415. public function ajaxFinish(array $form, FormStateInterface $form_state) {
  416. $response = new AjaxResponse();
  417. $response->addCommand(new CloseModalDialogCommand());
  418. return $response;
  419. }
  420. public function getRouteName() {
  421. return $this->routeMatch->getRouteName();
  422. }
  423. }