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