Merge pull request #17871 from seamuslee001/deprecated_jquery
[civicrm-core.git] / CRM / Core / StateMachine.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Core StateMachine.
20 *
21 * All state machines subclass for the core one for functionality specific to their needs.
22 *
23 * A state machine keeps track of different states and forms for a
24 * html quickform controller.
25 */
26 class CRM_Core_StateMachine {
27
28 /**
29 * The controller of this state machine.
30 * @var object
31 */
32 protected $_controller;
33
34 /**
35 * The list of states that belong to this state machine.
36 * @var array
37 */
38 protected $_states;
39
40 /**
41 * The list of pages that belong to this state machine. Note
42 * that a state and a form have a 1 <-> 1 relationship. so u
43 * can always derive one from the other
44 * @var array
45 */
46 protected $_pages;
47
48 /**
49 * The names of the pages.
50 *
51 * @var array
52 */
53 protected $_pageNames;
54
55 /**
56 * The mode that the state machine is operating in.
57 * @var int
58 */
59 protected $_action = NULL;
60
61 /**
62 * The display name for this machine.
63 * @var string
64 */
65 protected $_name = NULL;
66
67 /**
68 * Class constructor.
69 *
70 * @param object $controller
71 * The controller for this state machine.
72 *
73 * @param \const|int $action
74 *
75 * @return \CRM_Core_StateMachine
76 */
77 public function __construct(&$controller, $action = CRM_Core_Action::NONE) {
78 $this->_controller = &$controller;
79 $this->_action = $action;
80
81 $this->_states = [];
82 }
83
84 /**
85 * Getter for name.
86 *
87 * @return string
88 */
89 public function getName() {
90 return $this->_name;
91 }
92
93 /**
94 * Setter for name.
95 *
96 * @param string $name
97 */
98 public function setName($name) {
99 $this->_name = $name;
100 }
101
102 /**
103 * Do a state transition jump.
104 *
105 * Currently only supported types are
106 * Next and Back. The other actions (Cancel, Done, Submit etc) do
107 * not need the state machine to figure out where to go
108 *
109 * @param CRM_Core_Form $page
110 * The current form-page.
111 * @param string $actionName
112 * Current action name, as one Action object can serve multiple actions.
113 * @param string $type
114 * The type of transition being requested (Next or Back).
115 *
116 * @return object
117 */
118 public function perform(&$page, $actionName, $type = 'Next') {
119 // save the form values and validation status to the session
120 $page->isFormBuilt() or $page->buildForm();
121
122 $pageName = $page->getAttribute('name');
123 $data = &$page->controller->container();
124
125 $data['values'][$pageName] = $page->exportValues();
126 $data['valid'][$pageName] = $page->validate();
127
128 // if we are going to the next state
129 // Modal form and page is invalid: don't go further
130 if ($type == 'Next' && !$data['valid'][$pageName]) {
131 return $page->handle('display');
132 }
133
134 $state = &$this->_states[$pageName];
135
136 // dont know how or why we landed here so abort and display
137 // current page
138 if (empty($state)) {
139 return $page->handle('display');
140 }
141
142 // the page is valid, process it if we are jumping to the next state
143 if ($type == 'Next') {
144 $page->mainProcess();
145 // we get the state again, since postProcess might have changed it
146 // this bug took me forever to find :) Lobo
147 $state = &$this->_states[$pageName];
148 $state->handleNextState($page);
149 }
150 else {
151 $state->handleBackState($page);
152 }
153 }
154
155 /**
156 * Helper function to add a State to the state machine.
157 *
158 * @param string $name
159 * The internal name.
160 * @param int $type
161 * The type of state (START|FINISH|SIMPLE).
162 * @param object $prev
163 * The previous page if any.
164 * @param object $next
165 * The next page if any.
166 */
167 public function addState($name, $type, $prev, $next) {
168 $this->_states[$name] = new CRM_Core_State($name, $type, $prev, $next, $this);
169 }
170
171 /**
172 * Given a name find the corresponding state.
173 *
174 * @param string $name
175 * The state name.
176 *
177 * @return object
178 * the state object
179 */
180 public function find($name) {
181 if (array_key_exists($name, $this->_states)) {
182 return $this->_states[$name];
183 }
184 else {
185 return NULL;
186 }
187 }
188
189 /**
190 * Return the list of state objects.
191 *
192 * @return array
193 * array of states in the state machine
194 */
195 public function getStates() {
196 return $this->_states;
197 }
198
199 /**
200 * Return the state object corresponding to the name.
201 *
202 * @param string $name
203 * Name of page.
204 *
205 * @return CRM_Core_State
206 * state object matching the name
207 */
208 public function &getState($name) {
209 if (isset($this->_states[$name])) {
210 return $this->_states[$name];
211 }
212
213 /*
214 * This is a gross hack for ajax driven requests where
215 * we change the form name to allow multiple edits to happen
216 * We need a cleaner way of doing this going forward
217 */
218 foreach ($this->_states as $n => $s) {
219 if (substr($name, 0, strlen($n)) == $n) {
220 return $s;
221 }
222 }
223
224 return NULL;
225 }
226
227 /**
228 * Return the list of form objects.
229 *
230 * @return array
231 * array of pages in the state machine
232 */
233 public function getPages() {
234 return $this->_pages;
235 }
236
237 /**
238 * Add sequential pages.
239 *
240 * Meta level function to create a simple wizard for a state machine that is completely sequential.
241 *
242 * @param array $pages
243 * (reference ) the array of page objects.
244 */
245 public function addSequentialPages(&$pages) {
246 $this->_pages = &$pages;
247 $numPages = count($pages);
248
249 $this->_pageNames = [];
250 foreach ($pages as $tempName => $value) {
251 if (!empty($value['className'])) {
252 $this->_pageNames[] = $tempName;
253 }
254 else {
255 $this->_pageNames[] = CRM_Utils_String::getClassName($tempName);
256 }
257 }
258
259 $i = 0;
260 foreach ($pages as $tempName => $value) {
261 $name = $this->_pageNames[$i];
262
263 $className = CRM_Utils_Array::value('className',
264 $value,
265 $tempName
266 );
267 $classPath = str_replace('_', '/', $className) . '.php';
268 if ($numPages == 1) {
269 $prev = $next = NULL;
270 $type = CRM_Core_State::START | CRM_Core_State::FINISH;
271 }
272 elseif ($i == 0) {
273 // start state
274 $prev = NULL;
275 $next = $this->_pageNames[$i + 1];
276 $type = CRM_Core_State::START;
277 }
278 elseif ($i == $numPages - 1) {
279 // finish state
280 $prev = $this->_pageNames[$i - 1];
281 $next = NULL;
282 $type = CRM_Core_State::FINISH;
283 }
284 else {
285 // in between simple state
286 $prev = $this->_pageNames[$i - 1];
287 $next = $this->_pageNames[$i + 1];
288 $type = CRM_Core_State::SIMPLE;
289 }
290
291 $this->addState($name, $type, $prev, $next);
292
293 $i++;
294 }
295 }
296
297 /**
298 * Reset the state machine.
299 */
300 public function reset() {
301 $this->_controller->reset();
302 }
303
304 /**
305 * Getter for action.
306 *
307 * @return int
308 */
309 public function getAction() {
310 return $this->_action;
311 }
312
313 /**
314 * Setter for content.
315 *
316 * @param string $content
317 * The content generated by this state machine.
318 */
319 public function setContent(&$content) {
320 $this->_controller->setContent($content);
321 }
322
323 /**
324 * Getter for content.
325 *
326 * @return string
327 */
328 public function &getContent() {
329 return $this->_controller->getContent();
330 }
331
332 /**
333 * @return mixed
334 */
335 public function getDestination() {
336 return $this->_controller->getDestination();
337 }
338
339 /**
340 * @return mixed
341 */
342 public function getSkipRedirection() {
343 return $this->_controller->getSkipRedirection();
344 }
345
346 /**
347 * @return mixed
348 */
349 public function fini() {
350 return $this->_controller->fini();
351 }
352
353 /**
354 * @return mixed
355 */
356 public function cancelAction() {
357 return $this->_controller->cancelAction();
358 }
359
360 /**
361 * Should the controller reset the session
362 * In some cases, specifically search we want to remember
363 * state across various actions and want to go back to the
364 * beginning from the final state, but retain the same session
365 * values
366 *
367 * @return bool
368 */
369 public function shouldReset() {
370 return TRUE;
371 }
372
373 }