3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
21 * All state machines subclass for the core one for functionality specific to their needs.
23 * A state machine keeps track of different states and forms for a
24 * html quickform controller.
26 class CRM_Core_StateMachine
{
29 * The controller of this state machine.
32 protected $_controller;
35 * The list of states that belong to this state machine.
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
49 * The names of the pages.
53 protected $_pageNames;
56 * The mode that the state machine is operating in.
59 protected $_action = NULL;
62 * The display name for this machine.
65 protected $_name = NULL;
70 * @param object $controller
71 * The controller for this state machine.
73 * @param \const|int $action
75 * @return \CRM_Core_StateMachine
77 public function __construct(&$controller, $action = CRM_Core_Action
::NONE
) {
78 $this->_controller
= &$controller;
79 $this->_action
= $action;
89 public function getName() {
98 public function setName($name) {
103 * Do a state transition jump.
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
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).
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();
122 $pageName = $page->getAttribute('name');
123 $data = &$page->controller
->container();
125 $data['values'][$pageName] = $page->exportValues();
126 $data['valid'][$pageName] = $page->validate();
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');
134 $state = &$this->_states
[$pageName];
136 // dont know how or why we landed here so abort and display
139 return $page->handle('display');
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);
151 $state->handleBackState($page);
156 * Helper function to add a State to the state machine.
158 * @param string $name
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.
167 public function addState($name, $type, $prev, $next) {
168 $this->_states
[$name] = new CRM_Core_State($name, $type, $prev, $next, $this);
172 * Given a name find the corresponding state.
174 * @param string $name
180 public function find($name) {
181 if (array_key_exists($name, $this->_states
)) {
182 return $this->_states
[$name];
190 * Return the list of state objects.
193 * array of states in the state machine
195 public function getStates() {
196 return $this->_states
;
200 * Return the state object corresponding to the name.
202 * @param string $name
205 * @return CRM_Core_State
206 * state object matching the name
208 public function &getState($name) {
209 if (isset($this->_states
[$name])) {
210 return $this->_states
[$name];
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
218 foreach ($this->_states
as $n => $s) {
219 if (substr($name, 0, strlen($n)) == $n) {
228 * Return the list of form objects.
231 * array of pages in the state machine
233 public function getPages() {
234 return $this->_pages
;
238 * Add sequential pages.
240 * Meta level function to create a simple wizard for a state machine that is completely sequential.
242 * @param array $pages
243 * (reference ) the array of page objects.
245 public function addSequentialPages(&$pages) {
246 $this->_pages
= &$pages;
247 $numPages = count($pages);
249 $this->_pageNames
= [];
250 foreach ($pages as $tempName => $value) {
251 if (!empty($value['className'])) {
252 $this->_pageNames
[] = $tempName;
255 $this->_pageNames
[] = CRM_Utils_String
::getClassName($tempName);
260 foreach ($pages as $tempName => $value) {
261 $name = $this->_pageNames
[$i];
263 $className = CRM_Utils_Array
::value('className',
267 $classPath = str_replace('_', '/', $className) . '.php';
268 if ($numPages == 1) {
269 $prev = $next = NULL;
270 $type = CRM_Core_State
::START | CRM_Core_State
::FINISH
;
275 $next = $this->_pageNames
[$i +
1];
276 $type = CRM_Core_State
::START
;
278 elseif ($i == $numPages - 1) {
280 $prev = $this->_pageNames
[$i - 1];
282 $type = CRM_Core_State
::FINISH
;
285 // in between simple state
286 $prev = $this->_pageNames
[$i - 1];
287 $next = $this->_pageNames
[$i +
1];
288 $type = CRM_Core_State
::SIMPLE
;
291 $this->addState($name, $type, $prev, $next);
298 * Reset the state machine.
300 public function reset() {
301 $this->_controller
->reset();
309 public function getAction() {
310 return $this->_action
;
314 * Setter for content.
316 * @param string $content
317 * The content generated by this state machine.
319 public function setContent(&$content) {
320 $this->_controller
->setContent($content);
324 * Getter for content.
328 public function &getContent() {
329 return $this->_controller
->getContent();
335 public function getDestination() {
336 return $this->_controller
->getDestination();
342 public function getSkipRedirection() {
343 return $this->_controller
->getSkipRedirection();
349 public function fini() {
350 return $this->_controller
->fini();
356 public function cancelAction() {
357 return $this->_controller
->cancelAction();
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
369 public function shouldReset() {