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