Update release-notes/5.52.1.md
[civicrm-core.git] / CRM / Core / StateMachine.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
8eedd10a 19 * Core StateMachine.
6a488035 20 *
8eedd10a 21 * All state machines subclass for the core one for functionality specific to their needs.
6a488035 22 *
8eedd10a 23 * A state machine keeps track of different states and forms for a
24 * html quickform controller.
6a488035
TO
25 */
26class CRM_Core_StateMachine {
27
28 /**
f9e31d7f 29 * The controller of this state machine.
6a488035
TO
30 * @var object
31 */
32 protected $_controller;
33
34 /**
f9e31d7f 35 * The list of states that belong to this state machine.
6a488035
TO
36 * @var array
37 */
38 protected $_states;
39
40 /**
100fef9d 41 * The list of pages that belong to this state machine. Note
6a488035
TO
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 /**
f9e31d7f 49 * The names of the pages.
6a488035
TO
50 *
51 * @var array
52 */
53 protected $_pageNames;
54
55 /**
f9e31d7f 56 * The mode that the state machine is operating in.
6a488035
TO
57 * @var int
58 */
59 protected $_action = NULL;
60
61 /**
f9e31d7f 62 * The display name for this machine.
6a488035
TO
63 * @var string
64 */
65 protected $_name = NULL;
66
67 /**
f9e31d7f 68 * Class constructor.
6a488035 69 *
6a0b768e
TO
70 * @param object $controller
71 * The controller for this state machine.
6a488035 72 *
da3c7979 73 * @param \const|int $action
dd244018
EM
74 *
75 * @return \CRM_Core_StateMachine
6a488035 76 */
00be9182 77 public function __construct(&$controller, $action = CRM_Core_Action::NONE) {
6a488035
TO
78 $this->_controller = &$controller;
79 $this->_action = $action;
80
be2fb01f 81 $this->_states = [];
6a488035
TO
82 }
83
84 /**
2e2605fe 85 * Getter for name.
6a488035
TO
86 *
87 * @return string
6a488035
TO
88 */
89 public function getName() {
90 return $this->_name;
91 }
92
93 /**
2e2605fe 94 * Setter for name.
6a488035 95 *
389bcebf 96 * @param string $name
6a488035
TO
97 */
98 public function setName($name) {
99 $this->_name = $name;
100 }
101
102 /**
2e2605fe
EM
103 * Do a state transition jump.
104 *
105 * Currently only supported types are
6a488035
TO
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 *
6a0b768e
TO
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).
6a488035 115 *
8eedd10a 116 * @return object
6a488035 117 */
00be9182 118 public function perform(&$page, $actionName, $type = 'Next') {
6a488035
TO
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') {
e6a0a301 144 $page->mainProcess();
6a488035
TO
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 /**
2e2605fe 156 * Helper function to add a State to the state machine.
6a488035 157 *
6a0b768e
TO
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.
6a488035 166 */
00be9182 167 public function addState($name, $type, $prev, $next) {
6a488035
TO
168 $this->_states[$name] = new CRM_Core_State($name, $type, $prev, $next, $this);
169 }
170
171 /**
2e2605fe 172 * Given a name find the corresponding state.
6a488035 173 *
6a0b768e
TO
174 * @param string $name
175 * The state name.
6a488035 176 *
a6c01b45
CW
177 * @return object
178 * the state object
6a488035 179 */
00be9182 180 public function find($name) {
6a488035
TO
181 if (array_key_exists($name, $this->_states)) {
182 return $this->_states[$name];
183 }
184 else {
185 return NULL;
186 }
187 }
188
189 /**
2e2605fe 190 * Return the list of state objects.
6a488035 191 *
a6c01b45
CW
192 * @return array
193 * array of states in the state machine
6a488035 194 */
00be9182 195 public function getStates() {
6a488035
TO
196 return $this->_states;
197 }
198
199 /**
f9e31d7f 200 * Return the state object corresponding to the name.
6a488035 201 *
6a0b768e
TO
202 * @param string $name
203 * Name of page.
6a488035 204 *
16b10e64
CW
205 * @return CRM_Core_State
206 * state object matching the name
6a488035 207 */
00be9182 208 public function &getState($name) {
6a488035 209 if (isset($this->_states[$name])) {
2aa397bc
TO
210 return $this->_states[$name];
211 }
6a488035
TO
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 */
481a74f4 218 foreach ($this->_states as $n => $s) {
6a488035
TO
219 if (substr($name, 0, strlen($n)) == $n) {
220 return $s;
221 }
222 }
223
2aa397bc 224 return NULL;
6a488035
TO
225 }
226
227 /**
f9e31d7f 228 * Return the list of form objects.
6a488035 229 *
a6c01b45
CW
230 * @return array
231 * array of pages in the state machine
6a488035 232 */
00be9182 233 public function getPages() {
6a488035
TO
234 return $this->_pages;
235 }
236
237 /**
2e2605fe 238 * Add sequential pages.
6a488035 239 *
2e2605fe 240 * Meta level function to create a simple wizard for a state machine that is completely sequential.
6a488035 241 *
6a0b768e
TO
242 * @param array $pages
243 * (reference ) the array of page objects.
6a488035 244 */
00be9182 245 public function addSequentialPages(&$pages) {
6a488035
TO
246 $this->_pages = &$pages;
247 $numPages = count($pages);
248
be2fb01f 249 $this->_pageNames = [];
6a488035 250 foreach ($pages as $tempName => $value) {
a7488080 251 if (!empty($value['className'])) {
6a488035
TO
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 /**
f9e31d7f 298 * Reset the state machine.
6a488035 299 */
00be9182 300 public function reset() {
6a488035
TO
301 $this->_controller->reset();
302 }
303
304 /**
f9e31d7f 305 * Getter for action.
6a488035
TO
306 *
307 * @return int
6a488035 308 */
00be9182 309 public function getAction() {
6a488035
TO
310 return $this->_action;
311 }
312
313 /**
f9e31d7f 314 * Setter for content.
6a488035 315 *
6a0b768e
TO
316 * @param string $content
317 * The content generated by this state machine.
6a488035 318 */
00be9182 319 public function setContent(&$content) {
6a488035
TO
320 $this->_controller->setContent($content);
321 }
322
323 /**
f9e31d7f 324 * Getter for content.
6a488035
TO
325 *
326 * @return string
6a488035 327 */
00be9182 328 public function &getContent() {
6a488035
TO
329 return $this->_controller->getContent();
330 }
331
a0ee3941
EM
332 /**
333 * @return mixed
334 */
00be9182 335 public function getDestination() {
6a488035
TO
336 return $this->_controller->getDestination();
337 }
338
a0ee3941
EM
339 /**
340 * @return mixed
341 */
00be9182 342 public function getSkipRedirection() {
6a488035
TO
343 return $this->_controller->getSkipRedirection();
344 }
345
a0ee3941
EM
346 /**
347 * @return mixed
348 */
00be9182 349 public function fini() {
6a488035
TO
350 return $this->_controller->fini();
351 }
352
a0ee3941
EM
353 /**
354 * @return mixed
355 */
00be9182 356 public function cancelAction() {
6a488035
TO
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 *
389bcebf 367 * @return bool
6a488035 368 */
00be9182 369 public function shouldReset() {
6a488035 370 return TRUE;
2aa397bc 371 }
6a488035
TO
372
373}