phpdoc params is always an array in api
[civicrm-core.git] / CRM / Core / StateMachine.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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
31 * @copyright CiviCRM LLC (c) 2004-2014
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 */
44 class CRM_Core_StateMachine {
45
46 /**
47 * The controller of this state machine
48 * @var object
49 */
50 protected $_controller;
51
52 /**
53 * The list of states that belong to this state machine
54 * @var array
55 */
56 protected $_states;
57
58 /**
59 * The list of pages that belong to this state machine. Note
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 /**
74 * The mode that the state machine is operating in
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 /**
86 * Class constructor
87 *
88 * @param object $controller
89 * The controller for this state machine.
90 *
91 * @param \const|int $action
92 *
93 * @return \CRM_Core_StateMachine
94 * @access public
95 */
96 public function __construct(&$controller, $action = CRM_Core_Action::NONE) {
97 $this->_controller = &$controller;
98 $this->_action = $action;
99
100 $this->_states = array();
101 }
102
103 /**
104 * Getter for name
105 *
106 * @return string
107 */
108 public function getName() {
109 return $this->_name;
110 }
111
112 /**
113 * Setter for name
114 *
115 * @param string
116 *
117 * @return void
118 */
119 public function setName($name) {
120 $this->_name = $name;
121 }
122
123 /**
124 * Do a state transition jump. Currently only supported types are
125 * Next and Back. The other actions (Cancel, Done, Submit etc) do
126 * not need the state machine to figure out where to go
127 *
128 * @param CRM_Core_Form $page
129 * The current form-page.
130 * @param string $actionName
131 * Current action name, as one Action object can serve multiple actions.
132 * @param string $type
133 * The type of transition being requested (Next or Back).
134 *
135 * @return void
136 */
137 public function perform(&$page, $actionName, $type = 'Next') {
138 // save the form values and validation status to the session
139 $page->isFormBuilt() or $page->buildForm();
140
141 $pageName = $page->getAttribute('name');
142 $data = &$page->controller->container();
143
144 $data['values'][$pageName] = $page->exportValues();
145 $data['valid'][$pageName] = $page->validate();
146
147 // if we are going to the next state
148 // Modal form and page is invalid: don't go further
149 if ($type == 'Next' && !$data['valid'][$pageName]) {
150 return $page->handle('display');
151 }
152
153 $state = &$this->_states[$pageName];
154
155 // dont know how or why we landed here so abort and display
156 // current page
157 if (empty($state)) {
158 return $page->handle('display');
159 }
160
161 // the page is valid, process it if we are jumping to the next state
162 if ($type == 'Next') {
163 $page->mainProcess();
164 // we get the state again, since postProcess might have changed it
165 // this bug took me forever to find :) Lobo
166 $state = &$this->_states[$pageName];
167 $state->handleNextState($page);
168 }
169 else {
170 $state->handleBackState($page);
171 }
172 }
173
174 /**
175 * Helper function to add a State to the state machine
176 *
177 * @param string $name
178 * The internal name.
179 * @param int $type
180 * The type of state (START|FINISH|SIMPLE).
181 * @param object $prev
182 * The previous page if any.
183 * @param object $next
184 * The next page if any.
185 *
186 * @return void
187 */
188 public function addState($name, $type, $prev, $next) {
189 $this->_states[$name] = new CRM_Core_State($name, $type, $prev, $next, $this);
190 }
191
192 /**
193 * Given a name find the corresponding state
194 *
195 * @param string $name
196 * The state name.
197 *
198 * @return object
199 * the state object
200 */
201 public function find($name) {
202 if (array_key_exists($name, $this->_states)) {
203 return $this->_states[$name];
204 }
205 else {
206 return NULL;
207 }
208 }
209
210 /**
211 * Return the list of state objects
212 *
213 * @return array
214 * array of states in the state machine
215 */
216 public function getStates() {
217 return $this->_states;
218 }
219
220 /**
221 * Return the state object corresponding to the name
222 *
223 * @param string $name
224 * Name of page.
225 *
226 * @return CRM_Core_State
227 * state object matching the name
228 */
229 public function &getState($name) {
230 if (isset($this->_states[$name])) {
231 return $this->_states[$name];
232 }
233
234 /*
235 * This is a gross hack for ajax driven requests where
236 * we change the form name to allow multiple edits to happen
237 * We need a cleaner way of doing this going forward
238 */
239 foreach ($this->_states as $n => $s) {
240 if (substr($name, 0, strlen($n)) == $n) {
241 return $s;
242 }
243 }
244
245 return NULL;
246 }
247
248 /**
249 * Return the list of form objects
250 *
251 * @return array
252 * array of pages in the state machine
253 */
254 public function getPages() {
255 return $this->_pages;
256 }
257
258 /**
259 * AddSequentialStates: meta level function to create a simple
260 * wizard for a state machine that is completely sequential.
261 *
262 *
263 * @param array $pages
264 * (reference ) the array of page objects.
265 *
266 * @internal param array $states states is an array of arrays. Each element
267 * of the top level array describes a state. Each state description
268 * includes the name, the display name and the class name
269 *
270 * @return void
271 */
272 public function addSequentialPages(&$pages) {
273 $this->_pages = &$pages;
274 $numPages = count($pages);
275
276 $this->_pageNames = array();
277 foreach ($pages as $tempName => $value) {
278 if (!empty($value['className'])) {
279 $this->_pageNames[] = $tempName;
280 }
281 else {
282 $this->_pageNames[] = CRM_Utils_String::getClassName($tempName);
283 }
284 }
285
286 $i = 0;
287 foreach ($pages as $tempName => $value) {
288 $name = $this->_pageNames[$i];
289
290 $className = CRM_Utils_Array::value('className',
291 $value,
292 $tempName
293 );
294 $classPath = str_replace('_', '/', $className) . '.php';
295 if ($numPages == 1) {
296 $prev = $next = NULL;
297 $type = CRM_Core_State::START | CRM_Core_State::FINISH;
298 }
299 elseif ($i == 0) {
300 // start state
301 $prev = NULL;
302 $next = $this->_pageNames[$i + 1];
303 $type = CRM_Core_State::START;
304 }
305 elseif ($i == $numPages - 1) {
306 // finish state
307 $prev = $this->_pageNames[$i - 1];
308 $next = NULL;
309 $type = CRM_Core_State::FINISH;
310 }
311 else {
312 // in between simple state
313 $prev = $this->_pageNames[$i - 1];
314 $next = $this->_pageNames[$i + 1];
315 $type = CRM_Core_State::SIMPLE;
316 }
317
318 $this->addState($name, $type, $prev, $next);
319
320 $i++;
321 }
322 }
323
324 /**
325 * Reset the state machine
326 *
327 * @return void
328 */
329 public function reset() {
330 $this->_controller->reset();
331 }
332
333 /**
334 * Getter for action
335 *
336 * @return int
337 */
338 public function getAction() {
339 return $this->_action;
340 }
341
342 /**
343 * Setter for content
344 *
345 * @param string $content
346 * The content generated by this state machine.
347 *
348 * @return void
349 */
350 public function setContent(&$content) {
351 $this->_controller->setContent($content);
352 }
353
354 /**
355 * Getter for content
356 *
357 * @return string
358 */
359 public function &getContent() {
360 return $this->_controller->getContent();
361 }
362
363 /**
364 * @return mixed
365 */
366 public function getDestination() {
367 return $this->_controller->getDestination();
368 }
369
370 /**
371 * @return mixed
372 */
373 public function getSkipRedirection() {
374 return $this->_controller->getSkipRedirection();
375 }
376
377 /**
378 * @return mixed
379 */
380 public function fini() {
381 return $this->_controller->fini();
382 }
383
384 /**
385 * @return mixed
386 */
387 public function cancelAction() {
388 return $this->_controller->cancelAction();
389 }
390
391 /**
392 * Should the controller reset the session
393 * In some cases, specifically search we want to remember
394 * state across various actions and want to go back to the
395 * beginning from the final state, but retain the same session
396 * values
397 *
398 * @return boolean
399 */
400 public function shouldReset() {
401 return TRUE;
402 }
403
404 }