From 05cb1faaae8c1457519ca7772a9f620151941c50 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Tue, 17 Aug 2010 19:48:24 -0700 Subject: [PATCH 01/48] Added documentation from bakery.cakephp.org --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f496379 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Simple Example + +Lets create a simple example to show how rapidly a multi-page form can be created with the WizardComponent. + +For this example, I am going to be creating a 4 step signup wizard that includes the following steps: + + 1. Account Info + 2. Mailing Address + 3. Billing Info + 4. Review + +There will also be a "confirmation" page at the end to confirm the user's signup. This is a very simple example and a wizard like it probably doesn't have a whole lot of real world usefulness, but I just want to demonstrate how the component is used and highlight a couple things as we go along. + +It is important to note that though we will using multiple models, the entire wizard will be contained in one controller. Also, I will be using the words 'step' and 'page' interchangeably - I'm merely referring to a page in the multi-page wizard. + +So after downloading wizard.php into our project's component folder, we include it in our controller's $components array just as we would any other component: + +## Controller Class: + +
<?php
+class SignupController extends AppController {
+	var $components = array('Wizard');
+}
+?>
+
+ +Next, we're going to setup our $steps array, which is an ordered list of steps for the wizard to follow. Each step will have its own view and will be processed by its own controller callback method. _There is also another optional callback for each step that will be discussed later._ + +The steps array is setup in your controller's beforeFilter(): + +
function beforeFilter() {
+	$this->Wizard->steps = array('account', 'address', 'billing', 'review');
+}
+
+ +The next step is to create the views used in the signup wizard. The names of the views correspond to steps names included in $steps (account.ctp, address.ctp, etc). I'll include the first view (account.ctp) just to highlight a couple things. + +## View Template: + +
<?php $form->create('Signup',array('id'=>'SignupForm','url'=>$this->here));?>
+	<h2>Step 1: Account Information</h2>
+	<ul>
+		<li><?php $form->input('Client.first_name', array('label'=>'First Name:','size'=>20,'div'=>false));?></li>
+		<li><?php $form->input('Client.last_name', array('label'=>'Last Name:','size'=>20,'div'=>false));?></li>
+		<li><?php $form->input('Client.phone', array('label'=>'Phone Number:','size'=>20,'div'=>false));?></li>
+	</ul>
+	<ul>
+		<li><?php $form->input('User.email', array('label'=>'Email:','size'=>20,'div'=>false));?></li>
+		<li><?php $form->input('User.password',array('label'=>'Password:','size'=>20,'div'=>false,));?></li>
+		<li><?php $form->input('User.confirm',array('label'=>'Confirm:','size'=>20,'div'=>false,'type'=>'password'));?></li>
+	</ul>
+	<div class="submit">
+		<?php $form->submit('Continue', array('div'=>false));?>
+		<?php $form->submit('Cancel', array('name'=>'Cancel','div'=>false));?>
+	</div>
+<?php $form->end();?>
+ +The first thing I want to point out is the url that the form is submitted to. Rather than submitting to the next step in the wizard, **each step submits to itself**, just as a normal form would do. (My favorite method is above : 'url'=>$this->here.) This is important because one of my main goals in creating this component was to allow the wizard to be easily setup and easily modified. This meant keeping the views divorced, as much as possible, from their inclusion or position in the steps array. _To further this goal, I have created a WizardHelper that will be published in the bakery soon. In the above example, "Step 1" would be replaced with the $wizard->stepNumber() method._ + +The second thing I wanted to highlight was the component's ability to handle data for multiple models (the same as single page forms). This is possible because every step has its own custom callback to process its data. \ No newline at end of file From 388e9e37555a7b5ec79d911cba145b03973bf940 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 18 Aug 2010 19:27:51 -0700 Subject: [PATCH 02/48] Updated method names --- controllers/components/wizard.php | 930 +++++++++++++++--------------- 1 file changed, 465 insertions(+), 465 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index 8c0e150..e8a892a 100755 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -1,466 +1,466 @@ - array('college', 'degree_type'), 'nodegree' => 'experience'), 'confirm'); - * - * The 'branchnames' (ie 'degree', 'nodegree') are arbitrary but used as selectors for the branch() and unbranch() methods. Branches - * can point to either another steps array or a single step. The first branch in a group that hasn't been skipped (see branch()) - * is included by default (if $defaultBranch = true). - * - * @var array - * @access public - */ - var $steps = array(); -/** - * Controller action that processes your step. - * - * @var string - * @access public - */ - var $wizardAction = 'wizard'; -/** - * Url to be redirected to after the wizard has been completed. - * Controller::afterComplete() is called directly before redirection. - * - * @var mixed - * @access public - */ - var $completeUrl = '/'; -/** - * Url to be redirected to after 'Cancel' submit button has been pressed by user. - * - * @var mixed - * @access public - */ - var $cancelUrl = '/'; -/** - * If true, the first "non-skipped" branch in a group will be used if a branch has - * not been included specifically. - * - * @var boolean - * @access public - */ - var $defaultBranch = true; -/** - * If true, the user will not be allowed to edit previously completed steps. They will be - * "locked down" to the current step. - * - * @var boolean - * @access public - */ - var $lockdown = false; -/** - * Internal step tracking. - * - * @var string - * @access protected - */ - var $_currentStep = null; -/** - * Holds the session key for data storage. - * - * @var string - * @access protected - */ - var $_sessionKey = null; -/** - * Other session keys used. - * - * @var string - * @access protected - */ - var $_configKey = null; - var $_branchKey = null; -/** - * Holds the array based url for redirecting. - * - * @var array - * @access protected - */ - var $_wizardUrl = array(); -/** - * Other components used. - * - * @var array - * @access public - */ - var $components = array('Session'); -/** - * Initializes WizardComponent for use in the controller - * - * @param object $controller A reference to the instantiating controller object - * @access public - */ - function initialize(&$controller) { - $this->controller =& $controller; - - $this->_sessionKey = $this->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name; - $this->_configKey = 'Wizard.config'; - $this->_branchKey = 'Wizard.branches.' . $controller->name; - } -/** - * Component startup method. - * - * @param object $controller A reference to the instantiating controller object - * @access public - */ - function startup(&$controller) { - $this->steps = $this->_parseSteps($this->steps); - - $this->config('wizardAction', $this->wizardAction); - $this->config('steps', $this->steps); - } -/** - * Main Component method. - * - * @param string $step Name of step associated in $this->steps to be processed. - * @access public - */ - function process($step) { - if (isset($this->controller->params['form']['Cancel'])) { - if (method_exists($this->controller, '_beforeCancel')) { - $this->controller->_beforeCancel($this->_getExpectedStep()); - } - $this->resetWizard(); - $this->controller->redirect($this->cancelUrl); - } - - if (empty($step)) { - if ($this->Session->check('Wizard.complete')) { - if (method_exists($this->controller, '_afterComplete')) { - $this->controller->_afterComplete(); - } - $this->resetWizard(); - $this->controller->redirect($this->completeUrl); - } - - $this->autoReset = false; - } elseif ($step == 'reset') { - if (!$this->lockdown) { - $this->resetWizard(); - } - } else { - if ($this->_validStep($step)) { - $this->_setCurrentStep($step); - - if (!empty($this->controller->data) && !isset($this->controller->params['form']['Previous'])) { - $proceed = false; - - $processCallback = '_' . Inflector::variable('process_' . $this->_currentStep); - if (method_exists($this->controller, $processCallback)) { - $proceed = $this->controller->$processCallback(); - } elseif ($this->autoValidate) { - $proceed = $this->_validateData(); - } else { - trigger_error(sprintf(__('Process Callback not found. Please create Controller::%s', true), $processCallback), E_USER_WARNING); - } - - if ($proceed) { - $this->save(); - - if (next($this->steps)) { - if ($this->autoAdvance) { - $this->redirect(); - } - $this->redirect(current($this->steps)); - } else { - $this->Session->write('Wizard.complete', $this->read()); - $this->resetWizard(); - - $this->controller->redirect($this->wizardAction); - } - } - } elseif (isset($this->controller->params['form']['Previous']) && prev($this->steps)) { - $this->redirect(current($this->steps)); - } elseif ($this->Session->check("$this->_sessionKey.$this->_currentStep")) { - $this->controller->data = $this->read($this->_currentStep); - } - - $prepareCallback = '_' . Inflector::variable('prepare_' . $this->_currentStep); - if (method_exists($this->controller, $prepareCallback)) { - $this->controller->$prepareCallback(); - } - - $this->config('activeStep', $this->_currentStep); - return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true; - } else { - trigger_error(sprintf(__('Step validation: %s is not a valid step.', true), $step), E_USER_WARNING); - } - } - - if ($step != 'reset' && $this->autoReset) { - $this->resetWizard(); - } - - $this->redirect(); - } -/** - * Selects a branch to be used in the steps array. The first branch in a group is included by default. - * - * @param string $name Branch name to be included in steps. - * @param boolean $skip Branch will be skipped instead of included if true. - * @access public - */ - function branch($name, $skip = false) { - $branches = array(); - - if ($this->Session->check($this->_branchKey)) { - $branches = $this->Session->read($this->_branchKey); - } - - if (isset($branches[$name])) { - unset($branches[$name]); - } - - $value = $skip ? 'skip' : 'branch'; - $branches[$name] = $value; - - $this->Session->write($this->_branchKey, $branches); - } -/** - * Saves configuration details for use in WizardHelper or returns a config value. - * This is method usually handled only by the component. - * - * @param string $name Name of configuration variable. - * @param mixed $value Value to be stored. - * @return mixed - * @access public - */ - function config($name, $value = null) { - if ($value == null) { - return $this->Session->read("$this->_configKey.$name"); - } - $this->Session->write("$this->_configKey.$name", $value); - } -/** - * Get the data from the Session that has been stored by the WizardComponent. - * - * @param mixed $name The name of the session variable (or a path as sent to Set.extract) - * @return mixed The value of the session variable - * @access public - */ - function read($key = null) { - if ($key == null) { - return $this->Session->read($this->_sessionKey); - } else { - $wizardData = $this->Session->read("$this->_sessionKey.$key"); - if (!empty($wizardData)) { - return $wizardData; - } else { - return null; - } - } - } -/** - * Handles Wizard redirection. A null url will redirect to the "expected" step. - * - * @param string $step Stepname to be redirected to. - * @param integer $status Optional HTTP status code (eg: 404) - * @param boolean $exit If true, exit() will be called after the redirect - * @see Controller::redirect() - * @access public - */ - function redirect($step = null, $status = null, $exit = true) { - if ($step == null) { - $step = $this->_getExpectedStep(); - } - $url = array('controller' => $this->controller->name, 'action' => $this->wizardAction, $step); - $this->controller->redirect($url, $status, $exit); - } -/** - * Resets the wizard by deleting the wizard session. - * - * @access public - */ - function resetWizard() { - $this->Session->del($this->_branchKey); - $this->Session->del($this->_sessionKey); - } -/** - * Saves the data from the current step into the Session. - * - * Please note: This is normally called automatically by the component after - * a successful _processCallback, but can be called directly for advanced navigation purposes. - * - * @access public - */ - function save() { - $this->Session->write("$this->_sessionKey.$this->_currentStep", $this->controller->data); - } -/** - * Removes a branch from the steps array. - * - * @param string $branch Name of branch to be removed from steps array. - * @access public - */ - function unbranch($branch) { - $this->Session->del("$this->_branchKey.$branch"); - } -/** - * Finds the first incomplete step (i.e. step data not saved in Session). - * - * @return string $step or false if complete - * @access protected - */ - function _getExpectedStep() { - foreach ($this->steps as $step) { - if (!$this->Session->check("$this->_sessionKey.$step")) { - $this->config('expectedStep', $step); - return $step; - } - } - return false; - } -/** - * Saves configuration details for use in WizardHelper. - * - * @return mixed - * @access protected - */ - function _branchType($branch) { - if ($this->Session->check("$this->_branchKey.$branch")) { - return $this->Session->read("$this->_branchKey.$branch"); - } - return false; - } -/** - * Parses the steps array by stripping off nested arrays not included in the branches - * and returns a simple array with the correct steps. - * - * @param array $steps Array to be parsed for nested arrays and returned as simple array. - * @return array - * @access protected - */ - function _parseSteps($steps) { - $parsed = array(); - - foreach ($steps as $key => $name) { - if (is_array($name)) { - foreach ($name as $branchName => $step) { - $branchType = $this->_branchType($branchName); - - if ($branchType) { - if ($branchType !== 'skip') { - $branch = $branchName; - } - } elseif (empty($branch) && $this->defaultBranch) { - $branch = $branchName; - } - } - - if (!empty($branch)) { - if (is_array($name[$branch])) { - $parsed = array_merge($parsed, $this->_parseSteps($name[$branch])); - } else { - $parsed[] = $name[$branch]; - } - } - } else { - $parsed[] = $name; - } - } - return $parsed; - } -/** - * Moves internal array pointer of $this->steps to $step and sets $this->_currentStep. - * - * @param $step Step to point to. - * @access protected - */ - function _setCurrentStep($step) { - $this->_currentStep = reset($this->steps); - - while(current($this->steps) != $step) { - $this->_currentStep = next($this->steps); - } - } -/** - * Validates controller data with the correct model if the model is included in - * the controller's uses array. This only occurs if $autoValidate = true and there - * is no processCallback in the controller for the current step. - * - * @return boolean - * @access protected - */ - function _validateData() { - $controller =& $this->controller; - - foreach ($controller->data as $model => $data) { - if (in_array($model, $controller->uses)) { - $controller->{$model}->set($data); - - if (!$controller->{$model}->validates()) { - return false; - } - } - } - return true; - } -/** - * Validates the $step in two ways: - * 1. Validates that the step exists in $this->steps array. - * 2. Validates that the step is either before or exactly the expected step. - * - * @param $step Step to validate. - * @return mixed - * @access protected - */ - function _validStep($step) { - if (in_array($step, $this->steps)) { - if ($this->lockdown) { - return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps)); - } - return (array_search($step, $this->steps) <= array_search($this->_getExpectedStep(), $this->steps)); - } - return false; - } -} + array('college', 'degree_type'), 'nodegree' => 'experience'), 'confirm'); + * + * The 'branchnames' (ie 'degree', 'nodegree') are arbitrary but used as selectors for the branch() and unbranch() methods. Branches + * can point to either another steps array or a single step. The first branch in a group that hasn't been skipped (see branch()) + * is included by default (if $defaultBranch = true). + * + * @var array + * @access public + */ + var $steps = array(); +/** + * Controller action that processes your step. + * + * @var string + * @access public + */ + var $action = 'wizard'; +/** + * Url to be redirected to after the wizard has been completed. + * Controller::afterComplete() is called directly before redirection. + * + * @var mixed + * @access public + */ + var $completeUrl = '/'; +/** + * Url to be redirected to after 'Cancel' submit button has been pressed by user. + * + * @var mixed + * @access public + */ + var $cancelUrl = '/'; +/** + * If true, the first "non-skipped" branch in a group will be used if a branch has + * not been included specifically. + * + * @var boolean + * @access public + */ + var $defaultBranch = true; +/** + * If true, the user will not be allowed to edit previously completed steps. They will be + * "locked down" to the current step. + * + * @var boolean + * @access public + */ + var $lockdown = false; +/** + * Internal step tracking. + * + * @var string + * @access protected + */ + var $_currentStep = null; +/** + * Holds the session key for data storage. + * + * @var string + * @access protected + */ + var $_sessionKey = null; +/** + * Other session keys used. + * + * @var string + * @access protected + */ + var $_configKey = null; + var $_branchKey = null; +/** + * Holds the array based url for redirecting. + * + * @var array + * @access protected + */ + var $_wizardUrl = array(); +/** + * Other components used. + * + * @var array + * @access public + */ + var $components = array('Session'); +/** + * Initializes WizardComponent for use in the controller + * + * @param object $controller A reference to the instantiating controller object + * @access public + */ + function initialize(&$controller) { + $this->controller =& $controller; + + $this->_sessionKey = $this->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name; + $this->_configKey = 'Wizard.config'; + $this->_branchKey = 'Wizard.branches.' . $controller->name; + } +/** + * Component startup method. + * + * @param object $controller A reference to the instantiating controller object + * @access public + */ + function startup(&$controller) { + $this->steps = $this->_parseSteps($this->steps); + + $this->config('action', $this->action); + $this->config('steps', $this->steps); + } +/** + * Main Component method. + * + * @param string $step Name of step associated in $this->steps to be processed. + * @access public + */ + function process($step) { + if (isset($this->controller->params['form']['Cancel'])) { + if (method_exists($this->controller, '_beforeCancel')) { + $this->controller->_beforeCancel($this->_getExpectedStep()); + } + $this->resetWizard(); + $this->controller->redirect($this->cancelUrl); + } + + if (empty($step)) { + if ($this->Session->check('Wizard.complete')) { + if (method_exists($this->controller, '_afterComplete')) { + $this->controller->_afterComplete(); + } + $this->resetWizard(); + $this->controller->redirect($this->completeUrl); + } + + $this->autoReset = false; + } elseif ($step == 'reset') { + if (!$this->lockdown) { + $this->resetWizard(); + } + } else { + if ($this->_validStep($step)) { + $this->_setCurrentStep($step); + + if (!empty($this->controller->data) && !isset($this->controller->params['form']['Previous'])) { + $proceed = false; + + $processCallback = '_' . Inflector::variable('process_' . $this->_currentStep); + if (method_exists($this->controller, $processCallback)) { + $proceed = $this->controller->$processCallback(); + } elseif ($this->autoValidate) { + $proceed = $this->_validateData(); + } else { + trigger_error(sprintf(__('Process Callback not found. Please create Controller::%s', true), $processCallback), E_USER_WARNING); + } + + if ($proceed) { + $this->save(); + + if (next($this->steps)) { + if ($this->autoAdvance) { + $this->redirect(); + } + $this->redirect(current($this->steps)); + } else { + $this->Session->write('Wizard.complete', $this->read()); + $this->resetWizard(); + + $this->controller->redirect($this->action); + } + } + } elseif (isset($this->controller->params['form']['Previous']) && prev($this->steps)) { + $this->redirect(current($this->steps)); + } elseif ($this->Session->check("$this->_sessionKey.$this->_currentStep")) { + $this->controller->data = $this->read($this->_currentStep); + } + + $prepareCallback = '_' . Inflector::variable('prepare_' . $this->_currentStep); + if (method_exists($this->controller, $prepareCallback)) { + $this->controller->$prepareCallback(); + } + + $this->config('activeStep', $this->_currentStep); + return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true; + } else { + trigger_error(sprintf(__('Step validation: %s is not a valid step.', true), $step), E_USER_WARNING); + } + } + + if ($step != 'reset' && $this->autoReset) { + $this->resetWizard(); + } + + $this->redirect(); + } +/** + * Selects a branch to be used in the steps array. The first branch in a group is included by default. + * + * @param string $name Branch name to be included in steps. + * @param boolean $skip Branch will be skipped instead of included if true. + * @access public + */ + function branch($name, $skip = false) { + $branches = array(); + + if ($this->Session->check($this->_branchKey)) { + $branches = $this->Session->read($this->_branchKey); + } + + if (isset($branches[$name])) { + unset($branches[$name]); + } + + $value = $skip ? 'skip' : 'branch'; + $branches[$name] = $value; + + $this->Session->write($this->_branchKey, $branches); + } +/** + * Saves configuration details for use in WizardHelper or returns a config value. + * This is method usually handled only by the component. + * + * @param string $name Name of configuration variable. + * @param mixed $value Value to be stored. + * @return mixed + * @access public + */ + function config($name, $value = null) { + if ($value == null) { + return $this->Session->read("$this->_configKey.$name"); + } + $this->Session->write("$this->_configKey.$name", $value); + } +/** + * Get the data from the Session that has been stored by the WizardComponent. + * + * @param mixed $name The name of the session variable (or a path as sent to Set.extract) + * @return mixed The value of the session variable + * @access public + */ + function read($key = null) { + if ($key == null) { + return $this->Session->read($this->_sessionKey); + } else { + $wizardData = $this->Session->read("$this->_sessionKey.$key"); + if (!empty($wizardData)) { + return $wizardData; + } else { + return null; + } + } + } +/** + * Handles Wizard redirection. A null url will redirect to the "expected" step. + * + * @param string $step Stepname to be redirected to. + * @param integer $status Optional HTTP status code (eg: 404) + * @param boolean $exit If true, exit() will be called after the redirect + * @see Controller::redirect() + * @access public + */ + function redirect($step = null, $status = null, $exit = true) { + if ($step == null) { + $step = $this->_getExpectedStep(); + } + $url = array('controller' => $this->controller->name, 'action' => $this->action, $step); + $this->controller->redirect($url, $status, $exit); + } +/** + * Resets the wizard by deleting the wizard session. + * + * @access public + */ + function reset() { + $this->Session->delete($this->_branchKey); + $this->Session->delete($this->_sessionKey); + } +/** + * Saves the data from the current step into the Session. + * + * Please note: This is normally called automatically by the component after + * a successful _processCallback, but can be called directly for advanced navigation purposes. + * + * @access public + */ + function save() { + $this->Session->write("$this->_sessionKey.$this->_currentStep", $this->controller->data); + } +/** + * Removes a branch from the steps array. + * + * @param string $branch Name of branch to be removed from steps array. + * @access public + */ + function unbranch($branch) { + $this->Session->delete("$this->_branchKey.$branch"); + } +/** + * Finds the first incomplete step (i.e. step data not saved in Session). + * + * @return string $step or false if complete + * @access protected + */ + function _getExpectedStep() { + foreach ($this->steps as $step) { + if (!$this->Session->check("$this->_sessionKey.$step")) { + $this->config('expectedStep', $step); + return $step; + } + } + return false; + } +/** + * Saves configuration details for use in WizardHelper. + * + * @return mixed + * @access protected + */ + function _branchType($branch) { + if ($this->Session->check("$this->_branchKey.$branch")) { + return $this->Session->read("$this->_branchKey.$branch"); + } + return false; + } +/** + * Parses the steps array by stripping off nested arrays not included in the branches + * and returns a simple array with the correct steps. + * + * @param array $steps Array to be parsed for nested arrays and returned as simple array. + * @return array + * @access protected + */ + function _parseSteps($steps) { + $parsed = array(); + + foreach ($steps as $key => $name) { + if (is_array($name)) { + foreach ($name as $branchName => $step) { + $branchType = $this->_branchType($branchName); + + if ($branchType) { + if ($branchType !== 'skip') { + $branch = $branchName; + } + } elseif (empty($branch) && $this->defaultBranch) { + $branch = $branchName; + } + } + + if (!empty($branch)) { + if (is_array($name[$branch])) { + $parsed = array_merge($parsed, $this->_parseSteps($name[$branch])); + } else { + $parsed[] = $name[$branch]; + } + } + } else { + $parsed[] = $name; + } + } + return $parsed; + } +/** + * Moves internal array pointer of $this->steps to $step and sets $this->_currentStep. + * + * @param $step Step to point to. + * @access protected + */ + function _setCurrentStep($step) { + $this->_currentStep = reset($this->steps); + + while(current($this->steps) != $step) { + $this->_currentStep = next($this->steps); + } + } +/** + * Validates controller data with the correct model if the model is included in + * the controller's uses array. This only occurs if $autoValidate = true and there + * is no processCallback in the controller for the current step. + * + * @return boolean + * @access protected + */ + function _validateData() { + $controller =& $this->controller; + + foreach ($controller->data as $model => $data) { + if (in_array($model, $controller->uses)) { + $controller->{$model}->set($data); + + if (!$controller->{$model}->validates()) { + return false; + } + } + } + return true; + } +/** + * Validates the $step in two ways: + * 1. Validates that the step exists in $this->steps array. + * 2. Validates that the step is either before or exactly the expected step. + * + * @param $step Step to validate. + * @return mixed + * @access protected + */ + function _validStep($step) { + if (in_array($step, $this->steps)) { + if ($this->lockdown) { + return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps)); + } + return (array_search($step, $this->steps) <= array_search($this->_getExpectedStep(), $this->steps)); + } + return false; + } +} ?> \ No newline at end of file From 8f1ae065b7b2781742c925b17301f323defd9509 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Thu, 19 Aug 2010 01:10:08 -0700 Subject: [PATCH 03/48] Minor updates to documentation --- views/helpers/wizard.php | 62 ++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/views/helpers/wizard.php b/views/helpers/wizard.php index e33cdbc..7ddfd7b 100644 --- a/views/helpers/wizard.php +++ b/views/helpers/wizard.php @@ -17,13 +17,13 @@ class WizardHelper extends AppHelper { var $helpers = array('Session','Html'); var $output = null; - -/** - * - * - * @param - * @return - */ + + /** + * undocumented function + * + * @param string $key optional key to retrieve the existing value + * @return mixed data at config key (if key is passed) + */ function config($key = null) { if ($key == null) { return $this->Session->read('Wizard.config'); @@ -36,12 +36,17 @@ function config($key = null) { } } } -/** - * - * - * @param - * @return - */ + + /** + * undocumented function + * + * @param string $title + * @param string $step + * @param string $htmlAttributes + * @param string $confirmMessage + * @param string $escapeTitle + * @return string link to a specific step + */ function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { if ($step == null) { $step = $title; @@ -50,12 +55,14 @@ function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = return $this->Html->link($title, $wizardAction.$step, $htmlAttributes, $confirmMessage, $escapeTitle); } -/** - * - * - * @param - * @return - */ + + /** + * Retrieve the step number of the specified step name, or the active step + * + * @param string $step optional name of step + * @param string $shiftIndex optional offset of returned array index. Default 1 + * @return string step number. Returns false if not found + */ function stepNumber($step = null, $shiftIndex = 1) { if ($step == null) { $step = $this->config('activeStep'); @@ -69,12 +76,17 @@ function stepNumber($step = null, $shiftIndex = 1) { return false; } } -/** - * - * - * @param - * @return - */ + + /** + * Returns a set of html elements containing links for each step in the wizard. + * + * @param string $titles + * @param string $attributes pass a value for 'wrap' to change the default tag used + * @param string $htmlAttributes + * @param string $confirmMessage + * @param string $escapeTitle + * @return string + */ function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { $wizardConfig = $this->config(); extract($wizardConfig); From c4e6a0ed2c773583a2fdf18b9107171a4c668fb7 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Thu, 19 Aug 2010 02:13:04 -0700 Subject: [PATCH 04/48] Fixed glitch i made when i renamed the reset method --- controllers/components/wizard.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index e8a892a..7bde825 100755 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -169,7 +169,7 @@ function process($step) { if (method_exists($this->controller, '_beforeCancel')) { $this->controller->_beforeCancel($this->_getExpectedStep()); } - $this->resetWizard(); + $this->reset(); $this->controller->redirect($this->cancelUrl); } @@ -178,14 +178,14 @@ function process($step) { if (method_exists($this->controller, '_afterComplete')) { $this->controller->_afterComplete(); } - $this->resetWizard(); + $this->reset(); $this->controller->redirect($this->completeUrl); } $this->autoReset = false; } elseif ($step == 'reset') { if (!$this->lockdown) { - $this->resetWizard(); + $this->reset(); } } else { if ($this->_validStep($step)) { @@ -213,7 +213,7 @@ function process($step) { $this->redirect(current($this->steps)); } else { $this->Session->write('Wizard.complete', $this->read()); - $this->resetWizard(); + $this->reset(); $this->controller->redirect($this->action); } @@ -237,7 +237,7 @@ function process($step) { } if ($step != 'reset' && $this->autoReset) { - $this->resetWizard(); + $this->reset(); } $this->redirect(); From eac5f188076bd81d6e57dacb983604e41049829b Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 25 Aug 2010 22:05:19 -0700 Subject: [PATCH 05/48] Finished adding the rest of the docs. --- README2.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++ README3.md | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README4.md | 29 +++++++++++++++ README5.md | 47 +++++++++++++++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 README2.md create mode 100644 README3.md create mode 100644 README4.md create mode 100644 README5.md diff --git a/README2.md b/README2.md new file mode 100644 index 0000000..2d51355 --- /dev/null +++ b/README2.md @@ -0,0 +1,89 @@ +# Step 2: View Preparation and Data Processing + +Next we are going to setup our controller to handle each of the steps in the form wizard. + +*Very important:* Rather than creating a separate controller action for each of the steps in the form, all the steps are tied together through one action (the default is 'wizard'). This means, for our example, our urls will look like example.com/signup/wizard/account etc. This way, everything is handle by the component and customization is handled through controller callbacks. + +Because of this, the wizard action itself can be very basic. It merely needs to pass the step requested to the component's main method - process(): + +### Controller Class: + +
<?php 
+class SignupController extends AppController {
+	var $components = array('Wizard');
+
+	function beforeFilter() {
+		$this->Wizard->steps = array('account', 'address', 'billing', 'review');
+	}
+
+	function wizard($step = null) {
+		$this->Wizard->process($step);
+	}
+}
+?>
+ +Something to consider if your wizard is the controller's main feature (as it would be in our example), is to route the default action for the controller to the wizard action. This would allow prettier links such as example.com/signup to be handled by SignupController::wizard(), which would then redirect to /signup/wizard/account (or the first incomplete step in the wizard). + +
Router::connect('/signup', array('controller' => 'signup', 'action' => 'wizard'));
+ +Next, we are going to create controller callbacks to handle each step. Each step has two controller callbacks: prepare and process. + +The prepare callback is *optional* and occurs before the step's view is loaded. This is a good place to set any data or variables that you want available for the view. The name of the callback is prepareStepName. So for our example, our prepare callbacks would be prepareAccount(), prepareAddress(), etc. + +The process callback is *required* and occurs after data has been posted. This is where data validation should be handled. The process callback must return either true or false. If true, the wizard will continue to the next step; if false, the user will remain on the step and any validation errors will be presented. The name of the callback is processStepName. So for our example, our process callbacks would be processAccount(), processAddress(), etc. _You do not have to worry about retaining data as this is handled automatically by the component. Data retrieval will be discussed later in the tutorial._ + + +It's very important to note that every step in the wizard must contain a form with a field. The only way for the wizard to continue to the next step is for the process callback to return true. And the process callback is only called if $this->data is not empty. + +So lets create some basic process callbacks. Real world examples would most likely be more complicated, but this should give you the basic idea (don't forget to add any needed models): + +### Controller Class: + +
<?php 
+class SignupController extends AppController {
+	var $uses = array('Client', 'User', 'Billing');
+	var $components = array('Wizard');
+
+	function beforeFilter() {
+		$this->Wizard->steps = array('account', 'address', 'billing', 'review');
+	}
+
+	function wizard($step = null) {
+		$this->Wizard->process($step);
+	}
+/**
+ * [Wizard Process Callbacks]
+ */
+	function processAccount() {
+		$this->Client->set($this->data);
+		$this->User->set($this->data);
+
+		if($this->Client->validates() && $this->User->validates()) {
+			return true;
+		}
+		return false;
+	}
+
+	function processAddress() {
+		$this->Client->set($this->data);
+
+		if($this->Client->validates()) {
+			return true;
+		}
+		return false;
+	}
+
+	function processBilling() {
+		$this->Billing->set($this->data);
+
+		if($this->Billing->validates()) {
+			return true;
+		}
+		return false;
+	}
+
+	function processReview() {
+		return true;
+	}
+}
+?>
\ No newline at end of file diff --git a/README3.md b/README3.md new file mode 100644 index 0000000..864e163 --- /dev/null +++ b/README3.md @@ -0,0 +1,101 @@ +# Step 3: Data Retrieval and Wizard Completion + +At this point in the tutorial, your wizard should have of four steps - each consisting of a view and process callback (plus any optional prepare callbacks). Also, the wizard should be automatically handling data persistence and navigation between the steps. The next question is how to retrieve the data stored by the component and what happens at the completion of the wizard. + +## Data Retrieval + +Retrieving data from the component is possible at any point in the wizard. While our example will not manipulate or store the data permanently until the completion of the wizard, it's also reasonable that some applications may need to store data before the end of the wizard. For example, a job application may not be completed in one session but rather over a period of time. The progress, then, would need to be kept up with between sessions, rather than manipulated/stored all at once during the wizard completion. + +Wizard data is stored with the following path: sessionKey.stepName.modelName.fieldName. The sessionKey will be explained in the Wizard Completion section below. The component method for retrieving data is read($key = null) which works pretty much like SessionComponent::read() except that the sessionKey is handled automatically by the WizardComponent and doesn't need to be passed into read(). Passing null into read() returns all Wizard data. + +So, for example, if we wanted to do something with the client's email address (which was obtained in the account step) while processing the review step, we would use the following code: + +
function processReview() {
+	$email = $this->Wizard->read('account.User.email');
+	/* do something with the $email here */
+
+	return true;
+}
+ +An example showing how to retrieve all the current data with read() will be given below. + +## Wizard Completion + +One of my goals when writing this component was to prevent double submission of user data. One of the ways I accomplished this was by using the process callbacks for each step and redirecting to rather than rendering the next step. + +The second way was including an extra redirect and callback during the wizard completion process that creates a sort of "no man's land" for the wizard data. The way this works is, after the process callback for the last step is completed, the wizard data is moved to a new location in the session (Wizard.complete), the wizard redirects to a null step and another callback is called: _afterComplete(). + +_afterComplete() is an optional callback and is the ideal place to manipulate/store data after the wizard has been completed by the user. The callback does not need to return anything and the component automatically redirects to the $completeUrl (default '/') after the callback is finished. + +It's important to note that immediately after the afterComplete() callback and before the user is redirected to $completeUrl, the wizard is reset completely (all data is flushed from the session). If you need to redirect manually from _afterComplete(), be sure to call Wizard->reset() manually. + +So, to complete our tutorial example, we will pull all the data out of the wizard, store it in our database, and redirect the user to a confirmation page. + +### Controller Class: + +
Wizard->steps = array('account', 'address', 'billing', 'review');
+		$this->Wizard->completeUrl = '/signup/confirm';
+	}
+
+	function confirm() {
+	}
+
+	function wizard($step = null) {
+		$this->Wizard->process($step);
+	}
+/**
+ * [Wizard Process Callbacks]
+ */
+	function _processAccount() {
+		$this->Client->set($this->data);
+		$this->User->set($this->data);
+
+		if($this->Client->validates() && $this->User->validates()) {
+			return true;
+		}
+		return false;
+	}
+
+	function _processAddress() {
+		$this->Client->set($this->data);
+
+		if($this->Client->validates()) {
+			return true;
+		}
+		return false;
+	}
+
+	function _processBilling() {
+		$this->Billing->set($this->data);
+
+		if($this->Billing->validates()) {
+			return true;
+		}
+		return false;
+	}
+
+	function _processReview() {
+		return true;
+	}
+/**
+ * [Wizard Completion Callback]
+ */
+	function _afterComplete() {
+		$wizardData = $this->Wizard->read();
+		extract($wizardData);
+
+		$this->Client->save($account['Client'], false, array('first_name', 'last_name', 'phone'));
+		$this->User->save($account['User'], false, array('email', 'password'));
+		
+		... etc ...
+	}
+}
+?>
+ +Please note the addition to beforeFilter() and the new confirm() method. You would also need to create a view file (confirm.ctp) with something like "Congrats, your sign-up was successful!" etc. It would also be good to create some sort of token during the _afterComplete() callback and have it checked for in the confirm() method, but that's outside the scope of this tutorial. \ No newline at end of file diff --git a/README4.md b/README4.md new file mode 100644 index 0000000..fbe3572 --- /dev/null +++ b/README4.md @@ -0,0 +1,29 @@ +# Step 4: Plot-Branching Navigation + +A new addition to the WizardComponent 1.2 is *plot-branching navigation* (pbn). If you ever read a book as a child in which you interacted with the plot - i.e. If the knight slays the dragon, turn to page 64, if the knight runs for safety, turn to page 82. - then you've experienced pbn. In some applications, the steps in a wizard may not be a simple linear path, but might instead require the ability to "change course" based on user input. + +For example, a survey that has varying questions for men or women might ask gender on the first page and would then need to navigate to different pages depending on the answer. While this is a simple example, some wizards can become very complicated when all the different options occur at different points in the wizard and "paths" begin to cross. + +In some instances, it may not be a different path altogether, but merely a step being skipped over. Integrating Paypal Pro, for instance, requires the application allow the user to either enter their billing information on the site, or hop over to Paypal, login to their account and "skip" the billing page on the original site. + +## Advanced $steps Array + +When using pbn, the $steps array becomes a bit more complex. Instead of adding/removing steps on the fly, all the steps are included into the array like they normally would. Then, "branches" are selected or skipped using the component methods. The trick to understanding the WizardComponent's pbn implementation is understanding the $steps array - the rest is pretty simple. + +A simple $steps array is a single-tiered structure with each element corresponding to a step in the wizard. The array is ordered and the steps are handled sequentially. + +An advanced $steps array setup for pbn is a multi-tiered structure consisting of simple $steps arrays separated by branch arrays (or branch groups). The branch arrays are associative arrays with branch names as indexes and simple $steps arrays as elements. + +For example, lets say we had six steps: step1, step2, gender, step3, step4, and step5. The gender step would determine the user's gender and the subsequent steps would vary accordingly. If male, step3 and step4 would be used; if female, step4 and step5 would be used. So lets setup our $steps array: + +
function beforeFilter() {
+	$this->Wizard->steps = array('step1', 'step2', 'gender', array('male' => array('step3', 'step4'), 'female' => array('step4', 'step5')));
+}
+ +It's important to understand that there is almost always more than one way to accomplish the same effect with different $steps arrays. For example, I could have instead, setup a 'male' branch that used step3, included step4 for both, and then another branch for 'female' that would include step5. + +
function beforeFilter() {
+	$this->Wizard->steps = array('step1', 'step2', 'gender', array('male' => array('step3')), 'step4', array('female' => array('step5')));
+}
+ +Also, although these examples are simple, I should point out that the $steps array is not limited to a three-tiered array. As long as the pattern is followed - array(stepName, array(branchName => array(stepName, etc...))) - the steps array can be as complex as resources allow for. \ No newline at end of file diff --git a/README5.md b/README5.md new file mode 100644 index 0000000..a5065ab --- /dev/null +++ b/README5.md @@ -0,0 +1,47 @@ +# Step 5: PBN Component Methods + +After the the $steps array is setup, the question becomes, "How does the component navigate through all the branches?" This is done be selecting which branch will be used in a "branch group". By default, the first branch in a group is always used (unless it has been "skipped" - more on that later). You can turn this feature off by setting Wizard->defaultBranch = false. + +So, lets look at our two previous examples: + +
*Example 1:*
+$this->Wizard->steps = array('step1', 'step2', 'gender', array('male' => array('step3', 'step4'), 'female' => array('step4', 'step5')));
+
+*Example 2:*
+$this->Wizard->steps = array('step1', 'step2', 'gender', array('male' => array('step3')), 'step4', array('female' => array('step5')));
+ +In example 1, 'male' and 'female' are two branches in the same branch group. Therefore, without any interference, the component would automatically use the 'male' branch and 'female' would be skipped. The steps would occur: step1, step2, gender, step3, step4. If $defaultBranch = false, both would be skipped and the steps would occur: step1, step2, gender. + +In example 2, 'male' and 'female' are in separate branch groups. Therefore, without any interference, both branches would be used since they are the first branch in their respective groups. The steps would occur: step1, step2, gender, step3, step4, step5. If $defaultBranch = false, both would be skipped and the steps would occur: step1, step2, gender, step4. + +## branch() and unbranch() + +In order to specify to the component which branches should be used, you must use the branch() and unbranch() methods. The branch() method includes a branch (specified by its name) in the session and unbranch() removes a branch from the session. branch() also has an extra parameter that allows branches to be easily skipped - more on that below. + +So lets assume "female" was selected on the gender step. During the "processGender" callback, we could specify the "female" branch to be included: + +
function processGender() {
+	$this->Client->set($this->data);
+
+	if($this->Client->validates()) {
+		if($this->data['Client']['gender'] == 'female') {
+			 $this->Wizard->branch('female');
+		} else {
+			 $this->Wizard->branch('male');
+		}
+		return true;
+	}
+	return false;
+}
+ +In example 1, the 'female' branch would be used instead of the 'male' branch and the steps would occur: step1, step2, gender, step4, step5. However, in example 2, unless $defaultBranch = false, the 'male' branch would also be used since it is not in the same branch group as 'female'. + +Important: The first branch that has been included in the session will be used. In other words, if you were to do branch('male') and branch('female') for example 1, 'male' would be used since it occurs before 'female'. If 'male' was branched previously and you later wanted 'female' to be used, you would need to use unbranch('male'). + +In addition to including a branch to be used, branch() can also specify branches to be "skipped" by setting the second parameter to 'true'. If, for example, we used Wizard->branch('male', true) in the previous examples, 'male' would be skipped and 'female' would be used. The steps would occur: step1, step2, gender, step4, step5 - the same as using branch('female') with $defaultBranch = true! + +The last thing I want to mention about pbn is that branch names do not necessarily have to be unique. In fact, I'd imagine some complex pbn wizards could be solved with some creative branch naming schemes in which identical branch names would be used only one branch() would have to be called to alter multiple branch groups. For example, using branch('male') with the following $steps array would select the 'male' branches in both the first and second branch groups. + +
$steps = array('step1', array('male' => ..., 'female' => ...), 'step2', array('cyborg' => ..., 'male' => ..., 'alien' => ...)); 
+ +Also, (the other last thing I want to mention), the $steps array that each branch name points to can be treated exactly the same as the main $steps array - i.e. branch groups can be nested and branches are selected with branch() and $defaultBranch. \ No newline at end of file From e12712091122fdb7e918d4f6ec5108bd61c640c4 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Dec 2010 14:30:04 -0800 Subject: [PATCH 06/48] Created a Form->create() wrapper so the submit url is automatically set --- views/helpers/wizard.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/views/helpers/wizard.php b/views/helpers/wizard.php index 7ddfd7b..0e5bc79 100644 --- a/views/helpers/wizard.php +++ b/views/helpers/wizard.php @@ -15,7 +15,7 @@ * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ class WizardHelper extends AppHelper { - var $helpers = array('Session','Html'); + var $helpers = array('Session','Html','Form'); var $output = null; /** @@ -117,5 +117,18 @@ function progressMenu($titles = array(), $attributes = array(), $htmlAttributes return $this->output; } + + /** + * Wrapper for Form->create() + * + * @param string $model + * @param array $options + * @return string + */ + function create($model = null, $options = array()) { + if (!isset($options['url']) || !in_array($this->params['pass'][0], $options['url'])) + $options['url'][] = $this->params['pass'][0]; + return $this->Form->create($model, $options); + } } ?> \ No newline at end of file From ae7f7c471dc027422b210fe394b996916987801b Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Wed, 22 Dec 2010 14:50:37 -0800 Subject: [PATCH 07/48] Renamed 'wizardAction' attribute to 'action' --- controllers/components/wizard.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index f6f215b..e8dd967 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -64,7 +64,7 @@ class WizardComponent extends Object { * @var string * @access public */ - var $wizardAction = 'wizard'; + var $action = 'wizard'; /** * Url to be redirected to after the wizard has been completed. * Controller::afterComplete() is called directly before redirection. @@ -170,7 +170,7 @@ function initialize(&$controller, $settings = array()) { function startup(&$controller) { $this->steps = $this->_parseSteps($this->steps); - $this->config('wizardAction', $this->wizardAction); + $this->config('action', $this->action); $this->config('steps', $this->steps); if (!in_array('Wizard.Wizard', $this->controller->helpers) && !array_key_exists('Wizard.Wizard', $this->controller->helpers)) { @@ -243,7 +243,7 @@ function process($step) { $this->Session->write('Wizard.complete', $this->read()); $this->reset(); - $this->controller->redirect($this->wizardAction); + $this->controller->redirect($this->action); } } } elseif (isset($this->controller->params['form']['Previous']) && prev($this->steps)) { @@ -263,7 +263,7 @@ function process($step) { $this->config('activeStep', $this->_currentStep); if ($this->nestedViews) { - $this->controller->viewPath .= '/' . $this->wizardAction; + $this->controller->viewPath .= '/' . $this->action; } return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true; @@ -358,7 +358,7 @@ function redirect($step = null, $status = null, $exit = true) { if ($step == null) { $step = $this->_getExpectedStep(); } - $url = array('controller' => Inflector::underscore($this->controller->name), 'action' => $this->wizardAction, $step); + $url = array('controller' => Inflector::underscore($this->controller->name), 'action' => $this->action, $step); $this->controller->redirect($url, $status, $exit); } /** From 455c707c44fea5aa1a64dafc07c1402d974c8fea Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Thu, 23 Dec 2010 14:15:48 -0800 Subject: [PATCH 08/48] Better way to retrieve the controller name --- controllers/components/wizard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index e8dd967..877b516 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -358,7 +358,7 @@ function redirect($step = null, $status = null, $exit = true) { if ($step == null) { $step = $this->_getExpectedStep(); } - $url = array('controller' => Inflector::underscore($this->controller->name), 'action' => $this->action, $step); + $url = array('controller' => $this->controller->params['controller'], 'action' => $this->action, $step); $this->controller->redirect($url, $status, $exit); } /** From aa12f9a1eb80de87cab1d94da67d3a3211d0210f Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Thu, 23 Dec 2010 15:10:50 -0800 Subject: [PATCH 09/48] Made the completion redirect an array for more specific uses (such as locale) --- controllers/components/wizard.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index 877b516..364615f 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -242,8 +242,7 @@ function process($step) { } else { $this->Session->write('Wizard.complete', $this->read()); $this->reset(); - - $this->controller->redirect($this->action); + $this->controller->redirect(array('action' => $this->action)); } } } elseif (isset($this->controller->params['form']['Previous']) && prev($this->steps)) { From cf84595034f3e257d2a0cfc98ad1fba1fecb7153 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Mon, 24 Jan 2011 10:24:27 -0800 Subject: [PATCH 10/48] Instead of triggering an error when reaching a wrong step, jump to the correct step --- controllers/components/wizard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index 364615f..1df5f5c 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -267,7 +267,7 @@ function process($step) { return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true; } else { - trigger_error(sprintf(__('Step validation: %s is not a valid step.', true), $step), E_USER_WARNING); + $this->redirect(); } } From 525261a5b5c4c1fc9307b4217db73dc4d71a1a9a Mon Sep 17 00:00:00 2001 From: tomasm- Date: Fri, 6 May 2011 21:55:37 -0700 Subject: [PATCH 11/48] Adding missing variable to progressMenu. Adding stepTotal() convenience function to helper. --- views/helpers/wizard.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/views/helpers/wizard.php b/views/helpers/wizard.php index daf464f..8d50bcf 100644 --- a/views/helpers/wizard.php +++ b/views/helpers/wizard.php @@ -73,6 +73,13 @@ function stepNumber($step = null, $shiftIndex = 1) { return false; } } + + function stepTotal() + { + $steps = $this->config('steps'); + return count($steps); + } + /** * Returns a set of html elements containing links for each step in the wizard. * @@ -86,7 +93,8 @@ function stepNumber($step = null, $shiftIndex = 1) { function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { $wizardConfig = $this->config(); extract($wizardConfig); - + $wizardAction = $this->config('wizardAction'); + $attributes = array_merge(array('wrap' => 'div'), $attributes); extract($attributes); From 9c1818b9337a0e83954321f00db9aeb36ef08e4e Mon Sep 17 00:00:00 2001 From: philgagnon12 Date: Mon, 20 Feb 2012 10:45:39 -0500 Subject: [PATCH 12/48] CakePHP 2.0 adaptations : Wizard now extends 'Object' instead of 'Component' '$this->Session' replaced by , '$this->controller->Session' Added component dependency : function beforeRender(&$controller) { } AND function shutdown(&$controller) { } --- controllers/components/wizard.php | 47 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index 1df5f5c..fac29ee 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -157,7 +157,7 @@ function initialize(&$controller, $settings = array()) { $this->controller =& $controller; $this->_set($settings); - $this->_sessionKey = $this->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name; + $this->_sessionKey = $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name; $this->_configKey = 'Wizard.config'; $this->_branchKey = 'Wizard.branches.' . $controller->name; } @@ -202,7 +202,7 @@ function process($step) { } if (empty($step)) { - if ($this->Session->check('Wizard.complete')) { + if ($this->controller->Session->check('Wizard.complete')) { if (method_exists($this->controller, '_afterComplete')) { $this->controller->_afterComplete(); } @@ -240,17 +240,17 @@ function process($step) { } $this->redirect(current($this->steps)); } else { - $this->Session->write('Wizard.complete', $this->read()); + $this->controller->Session->write('Wizard.complete', $this->read()); $this->reset(); $this->controller->redirect(array('action' => $this->action)); } } } elseif (isset($this->controller->params['form']['Previous']) && prev($this->steps)) { $this->redirect(current($this->steps)); - } elseif ($this->Session->check("$this->_sessionKey._draft.current")) { + } elseif ($this->controller->Session->check("$this->_sessionKey._draft.current")) { $this->controller->data = $this->read('_draft.current.data'); - $this->Session->delete("$this->_sessionKey._draft.current"); - } elseif ($this->Session->check("$this->_sessionKey.$this->_currentStep")) { + $this->controller->Session->delete("$this->_sessionKey._draft.current"); + } elseif ($this->controller->Session->check("$this->_sessionKey.$this->_currentStep")) { $this->controller->data = $this->read($this->_currentStep); } @@ -287,8 +287,8 @@ function process($step) { function branch($name, $skip = false) { $branches = array(); - if ($this->Session->check($this->_branchKey)) { - $branches = $this->Session->read($this->_branchKey); + if ($this->controller->Session->check($this->_branchKey)) { + $branches = $this->controller->Session->read($this->_branchKey); } if (isset($branches[$name])) { @@ -298,7 +298,7 @@ function branch($name, $skip = false) { $value = $skip ? 'skip' : 'branch'; $branches[$name] = $value; - $this->Session->write($this->_branchKey, $branches); + $this->controller->Session->write($this->_branchKey, $branches); } /** * Saves configuration details for use in WizardHelper or returns a config value. @@ -311,9 +311,9 @@ function branch($name, $skip = false) { */ function config($name, $value = null) { if ($value == null) { - return $this->Session->read("$this->_configKey.$name"); + return $this->controller->Session->read("$this->_configKey.$name"); } - $this->Session->write("$this->_configKey.$name", $value); + $this->controller->Session->write("$this->_configKey.$name", $value); } /** * Loads previous draft session. @@ -338,9 +338,9 @@ function loadDraft($draft = array()) { */ function read($key = null) { if ($key == null) { - return $this->Session->read($this->_sessionKey); + return $this->controller->Session->read($this->_sessionKey); } else { - $wizardData = $this->Session->read("$this->_sessionKey.$key"); + $wizardData = $this->controller->Session->read("$this->_sessionKey.$key"); return !empty($wizardData) ? $wizardData : null; } } @@ -374,8 +374,8 @@ function resetWizard() { * @access public */ function reset() { - $this->Session->delete($this->_branchKey); - $this->Session->delete($this->_sessionKey); + $this->controller->Session->delete($this->_branchKey); + $this->controller->Session->delete($this->_sessionKey); } /** * Sets data into controller's wizard session. Particularly useful if the data @@ -385,7 +385,7 @@ function reset() { * @access public */ function restore($data = array()) { - $this->Session->write($this->_sessionKey, $data); + $this->controller->Session->write($this->_sessionKey, $data); } /** * Saves the data from the current step into the Session. @@ -402,7 +402,7 @@ function save($step = null, $data = null) { if (is_null($data)) { $data = $this->controller->data; } - $this->Session->write("$this->_sessionKey.$step", $data); + $this->controller->Session->write("$this->_sessionKey.$step", $data); } /** * Removes a branch from the steps array. @@ -411,7 +411,7 @@ function save($step = null, $data = null) { * @access public */ function unbranch($branch) { - $this->Session->delete("$this->_branchKey.$branch"); + $this->controller->Session->delete("$this->_branchKey.$branch"); } /** * Finds the first incomplete step (i.e. step data not saved in Session). @@ -421,7 +421,7 @@ function unbranch($branch) { */ function _getExpectedStep() { foreach ($this->steps as $step) { - if (!$this->Session->check("$this->_sessionKey.$step")) { + if (!$this->controller->Session->check("$this->_sessionKey.$step")) { $this->config('expectedStep', $step); return $step; } @@ -435,8 +435,8 @@ function _getExpectedStep() { * @access protected */ function _branchType($branch) { - if ($this->Session->check("$this->_branchKey.$branch")) { - return $this->Session->read("$this->_branchKey.$branch"); + if ($this->controller->Session->check("$this->_branchKey.$branch")) { + return $this->controller->Session->read("$this->_branchKey.$branch"); } return false; } @@ -531,5 +531,10 @@ function _validStep($step) { } return false; } + + + function beforeRender(&$controller) { } + + function shutdown(&$controller) { } } ?> \ No newline at end of file From 835b8fcaa786be64f2914a79645cb7e5a44acfbb Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Tue, 21 Feb 2012 00:57:39 -0800 Subject: [PATCH 13/48] Merged mikerogerz fixing #6 --- controllers/components/wizard.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index 1df5f5c..c5f7093 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -472,6 +472,8 @@ function _parseSteps($steps) { $parsed[] = $name[$branch]; } } + + unset($branch); } else { $parsed[] = $name; } From 6915c99d6294a76a62f1520117be9305db8e8278 Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Tue, 21 Feb 2012 01:03:21 -0800 Subject: [PATCH 14/48] Merging master branch from @kindred --- controllers/components/wizard.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index c5f7093..2bfd8ba 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -404,6 +404,21 @@ function save($step = null, $data = null) { } $this->Session->write("$this->_sessionKey.$step", $data); } + +/** + * Resets the data from the Session that has been stored by the WizardComponent. + * + * @param mixed $name The name of the session variable (or a path as sent to Set.extract) + * @access public + */ + function delete($key = null) { + if ($key == null) { + return; + } else { + $this->Session->delete("$this->_sessionKey.$key"); + return; + } + } /** * Removes a branch from the steps array. * From bd8e2d28ec7e149d775520c17660f69e28203dff Mon Sep 17 00:00:00 2001 From: philgagnon12 Date: Tue, 21 Feb 2012 15:34:10 -0500 Subject: [PATCH 15/48] Update controllers/components/wizard.php --- controllers/components/wizard.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index fac29ee..fe931c4 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -183,15 +183,15 @@ function startup(&$controller) { * @param string $step Name of step associated in $this->steps to be processed. * @access public */ - function process($step) { - if (isset($this->controller->params['form']['Cancel'])) { + function process($step) { + if (isset($this->controller->request->data['Cancel'])) { if (method_exists($this->controller, '_beforeCancel')) { $this->controller->_beforeCancel($this->_getExpectedStep()); } $this->reset(); $this->controller->redirect($this->cancelUrl); } - if (isset($this->controller->params['form']['Draft'])) { + if (isset($this->controller->request->data['Draft'])) { if (method_exists($this->controller, '_saveDraft')) { $draft = array('_draft' => array('current' => array('step' => $step, 'data' => $this->controller->data))); $this->controller->_saveDraft(array_merge_recursive((array)$this->read(), $draft)); @@ -219,7 +219,7 @@ function process($step) { if ($this->_validStep($step)) { $this->_setCurrentStep($step); - if (!empty($this->controller->data) && !isset($this->controller->params['form']['Previous'])) { + if (!empty($this->controller->data) && !isset($this->controller->request->data['Previous'])) { $proceed = false; $processCallback = '_' . Inflector::variable('process_' . $this->_currentStep); @@ -245,7 +245,7 @@ function process($step) { $this->controller->redirect(array('action' => $this->action)); } } - } elseif (isset($this->controller->params['form']['Previous']) && prev($this->steps)) { + } elseif (isset($this->controller->request->data['Previous']) && prev($this->steps)) { $this->redirect(current($this->steps)); } elseif ($this->controller->Session->check("$this->_sessionKey._draft.current")) { $this->controller->data = $this->read('_draft.current.data'); @@ -532,9 +532,13 @@ function _validStep($step) { return false; } + //Mendatory Component Method + function beforeRedirect(&$controller, $url, $status=null, $exit=true) { } + //Mendatory Component Method function beforeRender(&$controller) { } + //Mendatory Component Method function shutdown(&$controller) { } } ?> \ No newline at end of file From 8568e531eee3442e3f806fb5c3b9c855ea9cc555 Mon Sep 17 00:00:00 2001 From: philgagnon12 Date: Tue, 21 Feb 2012 15:43:10 -0500 Subject: [PATCH 16/48] Update controllers/components/wizard.php --- controllers/components/wizard.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index fe931c4..cf21610 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -532,13 +532,11 @@ function _validStep($step) { return false; } - //Mendatory Component Method + //Mandatory Component Methods : function beforeRedirect(&$controller, $url, $status=null, $exit=true) { } - //Mendatory Component Method function beforeRender(&$controller) { } - //Mendatory Component Method function shutdown(&$controller) { } } ?> \ No newline at end of file From 9d687e13774029014d3054735915b24bed691b2e Mon Sep 17 00:00:00 2001 From: philgagnon12 Date: Tue, 21 Feb 2012 17:08:42 -0500 Subject: [PATCH 17/48] class WizardComponent extends Component { ... } function initialize($controller, $settings = array()) { $this->controller = $controller; Removed : Component::beforeRender($controller) Component::shutdown($controller) Component::beforeRedirect($controller, $url, $status=null, $exit=true) --- controllers/components/wizard.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/controllers/components/wizard.php b/controllers/components/wizard.php index cf21610..f933625 100644 --- a/controllers/components/wizard.php +++ b/controllers/components/wizard.php @@ -13,7 +13,7 @@ * @writtenby jaredhoyt * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ -class WizardComponent extends Object { +class WizardComponent extends Component { /** * The Component will redirect to the "expected step" after a step has been successfully * completed if autoAdvance is true. If false, the Component will redirect to @@ -153,8 +153,8 @@ class WizardComponent extends Object { * @param object $controller A reference to the instantiating controller object * @access public */ - function initialize(&$controller, $settings = array()) { - $this->controller =& $controller; + function initialize($controller, $settings = array()) { + $this->controller = $controller; $this->_set($settings); $this->_sessionKey = $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name; @@ -532,11 +532,6 @@ function _validStep($step) { return false; } - //Mandatory Component Methods : - function beforeRedirect(&$controller, $url, $status=null, $exit=true) { } - - function beforeRender(&$controller) { } - - function shutdown(&$controller) { } + } ?> \ No newline at end of file From 2b22e707c0e271c918c10eea16df081a7c4a8c7a Mon Sep 17 00:00:00 2001 From: Dean Sofer Date: Mon, 27 Feb 2012 11:23:47 -0800 Subject: [PATCH 18/48] Corrected folder structure to cake2.0 convention I tried to use git mv but apparently it couldn't tell it was just a relocation of the files :/ --- .../wizard.php => Controller/Component/WizardComponent.php | 0 views/helpers/wizard.php => View/Helper/WizardHelper.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename controllers/components/wizard.php => Controller/Component/WizardComponent.php (100%) rename views/helpers/wizard.php => View/Helper/WizardHelper.php (100%) diff --git a/controllers/components/wizard.php b/Controller/Component/WizardComponent.php similarity index 100% rename from controllers/components/wizard.php rename to Controller/Component/WizardComponent.php diff --git a/views/helpers/wizard.php b/View/Helper/WizardHelper.php similarity index 100% rename from views/helpers/wizard.php rename to View/Helper/WizardHelper.php From fbbcd844bd64758e743ccbc07e7526f52e20d8d2 Mon Sep 17 00:00:00 2001 From: destinydriven Date: Fri, 6 Jul 2012 22:02:43 -0300 Subject: [PATCH 19/48] $this->params() has been replaced by $this->request in Cake 2.* --- Controller/Component/WizardComponent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php index d81983e..194661d 100644 --- a/Controller/Component/WizardComponent.php +++ b/Controller/Component/WizardComponent.php @@ -357,7 +357,7 @@ function redirect($step = null, $status = null, $exit = true) { if ($step == null) { $step = $this->_getExpectedStep(); } - $url = array('controller' => $this->controller->params['controller'], 'action' => $this->action, $step); + $url = array('controller' => $this->controller->request->controller, 'action' => $this->action, $step); $this->controller->redirect($url, $status, $exit); } /** From e4029fc0b52087146db850bddf4a3ac2da6201d3 Mon Sep 17 00:00:00 2001 From: destinydriven Date: Wed, 25 Jul 2012 16:26:15 -0300 Subject: [PATCH 20/48] Update Controller/Component/WizardComponent.php Added visibility keywords --- Controller/Component/WizardComponent.php | 74 ++++++++++++------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php index 194661d..9584c09 100644 --- a/Controller/Component/WizardComponent.php +++ b/Controller/Component/WizardComponent.php @@ -24,7 +24,7 @@ class WizardComponent extends Component { * @var boolean * @access public */ - var $autoAdvance = true; + public $autoAdvance = true; /** * Option to automatically reset if the wizard does not follow "normal" * operation. (ie. manual url changing, navigation away and returning, etc.) @@ -34,7 +34,7 @@ class WizardComponent extends Component { * @var boolean * @access public */ - var $autoReset = false; + public $autoReset = false; /** * If no processCallback() exists for the current step, the component will automatically * validate the model data against the models included in the controller's uses array. @@ -42,7 +42,7 @@ class WizardComponent extends Component { * @var boolean * @access public */ - var $autoValidate = false; + public $autoValidate = false; /** * List of steps, in order, that are to be included in the wizard. * basic example: $steps = array('contact', 'payment', 'confirm'); @@ -57,14 +57,14 @@ class WizardComponent extends Component { * @var array * @access public */ - var $steps = array(); + public $steps = array(); /** * Controller action that processes your step. * * @var string * @access public */ - var $action = 'wizard'; + public $action = 'wizard'; /** * Url to be redirected to after the wizard has been completed. * Controller::afterComplete() is called directly before redirection. @@ -72,21 +72,21 @@ class WizardComponent extends Component { * @var mixed * @access public */ - var $completeUrl = '/'; + public $completeUrl = '/'; /** * Url to be redirected to after 'Cancel' submit button has been pressed by user. * * @var mixed * @access public */ - var $cancelUrl = '/'; + public $cancelUrl = '/'; /** * Url to be redirected to after 'Draft' submit button has been pressed by user. * * @var mixed * @access public */ - var $draftUrl = '/'; + public $draftUrl = '/'; /** * If true, the first "non-skipped" branch in a group will be used if a branch has * not been included specifically. @@ -94,7 +94,7 @@ class WizardComponent extends Component { * @var boolean * @access public */ - var $defaultBranch = true; + public $defaultBranch = true; /** * If true, the user will not be allowed to edit previously completed steps. They will be * "locked down" to the current step. @@ -102,7 +102,7 @@ class WizardComponent extends Component { * @var boolean * @access public */ - var $lockdown = false; + public $lockdown = false; /** * If true, the component will render views found in views/{wizardAction}/{step}.ctp rather * than views/{step}.ctp. @@ -110,50 +110,50 @@ class WizardComponent extends Component { * @var boolean * @access public */ - var $nestedViews = false; + public $nestedViews = false; /** * Internal step tracking. * * @var string * @access protected */ - var $_currentStep = null; + public $_currentStep = null; /** * Holds the session key for data storage. * * @var string * @access protected */ - var $_sessionKey = null; + public $_sessionKey = null; /** * Other session keys used. * * @var string * @access protected */ - var $_configKey = null; - var $_branchKey = null; + public $_configKey = null; + public $_branchKey = null; /** * Holds the array based url for redirecting. * * @var array * @access protected */ - var $_wizardUrl = array(); + public $_wizardUrl = array(); /** * Other components used. * * @var array * @access public */ - var $components = array('Session'); + public $components = array('Session'); /** * Initializes WizardComponent for use in the controller * * @param object $controller A reference to the instantiating controller object * @access public */ - function initialize($controller, $settings = array()) { + public function initialize($controller, $settings = array()) { $this->controller = $controller; $this->_set($settings); @@ -167,7 +167,7 @@ function initialize($controller, $settings = array()) { * @param object $controller A reference to the instantiating controller object * @access public */ - function startup(&$controller) { + public function startup(&$controller) { $this->steps = $this->_parseSteps($this->steps); $this->config('action', $this->action); @@ -183,7 +183,7 @@ function startup(&$controller) { * @param string $step Name of step associated in $this->steps to be processed. * @access public */ - function process($step) { + public function process($step) { if (isset($this->controller->request->data['Cancel'])) { if (method_exists($this->controller, '_beforeCancel')) { $this->controller->_beforeCancel($this->_getExpectedStep()); @@ -284,7 +284,7 @@ function process($step) { * @param boolean $skip Branch will be skipped instead of included if true. * @access public */ - function branch($name, $skip = false) { + public function branch($name, $skip = false) { $branches = array(); if ($this->controller->Session->check($this->_branchKey)) { @@ -309,7 +309,7 @@ function branch($name, $skip = false) { * @return mixed * @access public */ - function config($name, $value = null) { + public function config($name, $value = null) { if ($value == null) { return $this->controller->Session->read("$this->_configKey.$name"); } @@ -322,7 +322,7 @@ function config($name, $value = null) { * @see WizardComponent::process() * @access public */ - function loadDraft($draft = array()) { + public function loadDraft($draft = array()) { if (!empty($draft['_draft']['current']['step'])) { $this->restore($draft); $this->redirect($draft['_draft']['current']['step']); @@ -336,7 +336,7 @@ function loadDraft($draft = array()) { * @return mixed The value of the session variable * @access public */ - function read($key = null) { + public function read($key = null) { if ($key == null) { return $this->controller->Session->read($this->_sessionKey); } else { @@ -353,7 +353,7 @@ function read($key = null) { * @see Controller::redirect() * @access public */ - function redirect($step = null, $status = null, $exit = true) { + public function redirect($step = null, $status = null, $exit = true) { if ($step == null) { $step = $this->_getExpectedStep(); } @@ -365,7 +365,7 @@ function redirect($step = null, $status = null, $exit = true) { * * @access public */ - function resetWizard() { + public function resetWizard() { $this->reset(); } /** @@ -373,7 +373,7 @@ function resetWizard() { * * @access public */ - function reset() { + public function reset() { $this->controller->Session->delete($this->_branchKey); $this->controller->Session->delete($this->_sessionKey); } @@ -384,7 +384,7 @@ function reset() { * @param array $data Data to be written to controller's wizard session. * @access public */ - function restore($data = array()) { + public function restore($data = array()) { $this->controller->Session->write($this->_sessionKey, $data); } /** @@ -395,7 +395,7 @@ function restore($data = array()) { * * @access public */ - function save($step = null, $data = null) { + public function save($step = null, $data = null) { if (is_null($step)) { $step = $this->_currentStep; } @@ -411,7 +411,7 @@ function save($step = null, $data = null) { * @param mixed $name The name of the session variable (or a path as sent to Set.extract) * @access public */ - function delete($key = null) { + public function delete($key = null) { if ($key == null) { return; } else { @@ -425,7 +425,7 @@ function delete($key = null) { * @param string $branch Name of branch to be removed from steps array. * @access public */ - function unbranch($branch) { + public function unbranch($branch) { $this->controller->Session->delete("$this->_branchKey.$branch"); } /** @@ -434,7 +434,7 @@ function unbranch($branch) { * @return string $step or false if complete * @access protected */ - function _getExpectedStep() { + protected function _getExpectedStep() { foreach ($this->steps as $step) { if (!$this->controller->Session->check("$this->_sessionKey.$step")) { $this->config('expectedStep', $step); @@ -449,7 +449,7 @@ function _getExpectedStep() { * @return mixed * @access protected */ - function _branchType($branch) { + protected function _branchType($branch) { if ($this->controller->Session->check("$this->_branchKey.$branch")) { return $this->controller->Session->read("$this->_branchKey.$branch"); } @@ -463,7 +463,7 @@ function _branchType($branch) { * @return array * @access protected */ - function _parseSteps($steps) { + protected function _parseSteps($steps) { $parsed = array(); foreach ($steps as $key => $name) { @@ -501,7 +501,7 @@ function _parseSteps($steps) { * @param $step Step to point to. * @access protected */ - function _setCurrentStep($step) { + protected function _setCurrentStep($step) { $this->_currentStep = reset($this->steps); while(current($this->steps) != $step) { @@ -516,7 +516,7 @@ function _setCurrentStep($step) { * @return boolean * @access protected */ - function _validateData() { + protected function _validateData() { $controller =& $this->controller; foreach ($controller->data as $model => $data) { @@ -539,7 +539,7 @@ function _validateData() { * @return mixed * @access protected */ - function _validStep($step) { + protected function _validStep($step) { if (in_array($step, $this->steps)) { if ($this->lockdown) { return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps)); From 1c34870130717a4aa39c50a0e1a557a9df9fddb3 Mon Sep 17 00:00:00 2001 From: destinydriven Date: Wed, 25 Jul 2012 16:27:38 -0300 Subject: [PATCH 21/48] Update View/Helper/WizardHelper.php Added visibility keywords --- View/Helper/WizardHelper.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php index b53a091..7072d21 100644 --- a/View/Helper/WizardHelper.php +++ b/View/Helper/WizardHelper.php @@ -15,15 +15,15 @@ * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ class WizardHelper extends AppHelper { - var $helpers = array('Session','Html','Form'); - var $output = null; + public $helpers = array('Session','Html','Form'); + public $output = null; /** * undocumented function * * @param string $key optional key to retrieve the existing value * @return mixed data at config key (if key is passed) */ - function config($key = null) { + public function config($key = null) { if ($key == null) { return $this->Session->read('Wizard.config'); } else { @@ -45,7 +45,7 @@ function config($key = null) { * @param string $escapeTitle * @return string link to a specific step */ - function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { + public function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { if ($step == null) { $step = $title; } @@ -60,7 +60,7 @@ function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = * @param string $shiftIndex optional offset of returned array index. Default 1 * @return string step number. Returns false if not found */ - function stepNumber($step = null, $shiftIndex = 1) { + public function stepNumber($step = null, $shiftIndex = 1) { if ($step == null) { $step = $this->config('activeStep'); } @@ -74,7 +74,7 @@ function stepNumber($step = null, $shiftIndex = 1) { } } - function stepTotal() + public function stepTotal() { $steps = $this->config('steps'); return count($steps); @@ -90,7 +90,7 @@ function stepTotal() * @param string $escapeTitle * @return string */ - function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { + public function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) { $wizardConfig = $this->config(); extract($wizardConfig); $wizardAction = $this->config('wizardAction'); @@ -128,7 +128,7 @@ function progressMenu($titles = array(), $attributes = array(), $htmlAttributes * @param array $options * @return string */ - function create($model = null, $options = array()) { + public function create($model = null, $options = array()) { if (!isset($options['url']) || !in_array($this->params['pass'][0], $options['url'])) $options['url'][] = $this->params['pass'][0]; return $this->Form->create($model, $options); From cfcb978dd69f2b4711a412bebcd1fcf88f8ce481 Mon Sep 17 00:00:00 2001 From: destinydriven Date: Wed, 25 Jul 2012 16:28:47 -0300 Subject: [PATCH 22/48] Update README.md Added visibility keywords --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43d3dd0..56a3050 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Wizard plugin for CakePHP automates several aspects of multi-page forms incl * Clone/Copy the files in this directory into `app/plugins/wizard` * Include the wizard component in your controller: - * `var $components = array('Wizard.Wizard');` + * `public $components = array('Wizard.Wizard');` ## Documentation From 966c079c35c30f80cb16971998cc910497f47302 Mon Sep 17 00:00:00 2001 From: destinydriven Date: Wed, 25 Jul 2012 16:30:17 -0300 Subject: [PATCH 23/48] Update README2.md Added visibility keywords --- README2.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README2.md b/README2.md index 2d51355..0b113ba 100644 --- a/README2.md +++ b/README2.md @@ -10,13 +10,13 @@ Because of this, the wizard action itself can be very basic. It merely needs to
<?php 
 class SignupController extends AppController {
-	var $components = array('Wizard');
+	public $components = array('Wizard');
 
-	function beforeFilter() {
+	public function beforeFilter() {
 		$this->Wizard->steps = array('account', 'address', 'billing', 'review');
 	}
 
-	function wizard($step = null) {
+	public function wizard($step = null) {
 		$this->Wizard->process($step);
 	}
 }
@@ -41,20 +41,20 @@ So lets create some basic process callbacks. Real world examples would most like
 
 
<?php 
 class SignupController extends AppController {
-	var $uses = array('Client', 'User', 'Billing');
-	var $components = array('Wizard');
+	public $uses = array('Client', 'User', 'Billing');
+	public $components = array('Wizard');
 
-	function beforeFilter() {
+	public function beforeFilter() {
 		$this->Wizard->steps = array('account', 'address', 'billing', 'review');
 	}
 
-	function wizard($step = null) {
+	public function wizard($step = null) {
 		$this->Wizard->process($step);
 	}
 /**
  * [Wizard Process Callbacks]
  */
-	function processAccount() {
+	public function processAccount() {
 		$this->Client->set($this->data);
 		$this->User->set($this->data);
 
@@ -64,7 +64,7 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	function processAddress() {
+	public function processAddress() {
 		$this->Client->set($this->data);
 
 		if($this->Client->validates()) {
@@ -73,7 +73,7 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	function processBilling() {
+	public function processBilling() {
 		$this->Billing->set($this->data);
 
 		if($this->Billing->validates()) {
@@ -82,7 +82,7 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	function processReview() {
+	public function processReview() {
 		return true;
 	}
 }

From 3e9f1ce5c26eb237b0b9923e9950a5d271cb7ec8 Mon Sep 17 00:00:00 2001
From: destinydriven 
Date: Wed, 25 Jul 2012 16:33:21 -0300
Subject: [PATCH 24/48] Update README3.md

Added visibility keywords
---
 README3.md | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/README3.md b/README3.md
index 864e163..cd4acd6 100644
--- a/README3.md
+++ b/README3.md
@@ -35,24 +35,24 @@ So, to complete our tutorial example, we will pull all the data out of the wizar
 
 
Wizard->steps = array('account', 'address', 'billing', 'review');
 		$this->Wizard->completeUrl = '/signup/confirm';
 	}
 
-	function confirm() {
+	public function confirm() {
 	}
 
-	function wizard($step = null) {
+	public function wizard($step = null) {
 		$this->Wizard->process($step);
 	}
 /**
  * [Wizard Process Callbacks]
  */
-	function _processAccount() {
+	protected function _processAccount() {
 		$this->Client->set($this->data);
 		$this->User->set($this->data);
 
@@ -62,7 +62,7 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	function _processAddress() {
+	protected function _processAddress() {
 		$this->Client->set($this->data);
 
 		if($this->Client->validates()) {
@@ -71,7 +71,7 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	function _processBilling() {
+	protected function _processBilling() {
 		$this->Billing->set($this->data);
 
 		if($this->Billing->validates()) {
@@ -80,13 +80,13 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	function _processReview() {
+	protected function _processReview() {
 		return true;
 	}
 /**
  * [Wizard Completion Callback]
  */
-	function _afterComplete() {
+	protected function _afterComplete() {
 		$wizardData = $this->Wizard->read();
 		extract($wizardData);
 

From 87abd39861d900b19ae052598beaa6d70f72ac96 Mon Sep 17 00:00:00 2001
From: destinydriven 
Date: Wed, 25 Jul 2012 16:34:01 -0300
Subject: [PATCH 25/48] Update README3.md

Added visibility keywords
---
 README3.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README3.md b/README3.md
index cd4acd6..fb2dd2d 100644
--- a/README3.md
+++ b/README3.md
@@ -10,7 +10,7 @@ Wizard data is stored with the following path: sessionKey.stepName.modelName.fie
 
 So, for example, if we wanted to do something with the client's email address (which was obtained in the account step) while processing the review step, we would use the following code:
 
-
function processReview() {
+
public function processReview() {
 	$email = $this->Wizard->read('account.User.email');
 	/* do something with the $email here */
 

From b0f38eb5663fd3a9787350eb010720aac15734a2 Mon Sep 17 00:00:00 2001
From: destinydriven 
Date: Wed, 25 Jul 2012 16:40:55 -0300
Subject: [PATCH 26/48] Update README4.md

Added visibility keywords
---
 README4.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README4.md b/README4.md
index fbe3572..a1362b8 100644
--- a/README4.md
+++ b/README4.md
@@ -16,13 +16,13 @@ An advanced $steps array setup for pbn is a multi-tiered structure consisting of
 
 For example, lets say we had six steps: step1, step2, gender, step3, step4, and step5. The gender step would determine the user's gender and the subsequent steps would vary accordingly. If male, step3 and step4 would be used; if female, step4 and step5 would be used. So lets setup our $steps array:
 
-
function beforeFilter() {
+
public function beforeFilter() {
 	$this->Wizard->steps = array('step1', 'step2', 'gender', array('male' => array('step3', 'step4'), 'female' => array('step4', 'step5')));
 }
It's important to understand that there is almost always more than one way to accomplish the same effect with different $steps arrays. For example, I could have instead, setup a 'male' branch that used step3, included step4 for both, and then another branch for 'female' that would include step5. -
function beforeFilter() {
+
public function beforeFilter() {
 	$this->Wizard->steps = array('step1', 'step2', 'gender', array('male' => array('step3')), 'step4', array('female' => array('step5')));
 }
From 3fea909619bb3f1bd58515b60bd88cb106f00f91 Mon Sep 17 00:00:00 2001 From: destinydriven Date: Wed, 25 Jul 2012 16:42:18 -0300 Subject: [PATCH 27/48] Update README5.md Added visibility keywords --- README5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README5.md b/README5.md index a5065ab..11e2d6a 100644 --- a/README5.md +++ b/README5.md @@ -20,7 +20,7 @@ In order to specify to the component which branches should be used, you must use So lets assume "female" was selected on the gender step. During the "processGender" callback, we could specify the "female" branch to be included: -
function processGender() {
+
public  function processGender() {
 	$this->Client->set($this->data);
 
 	if($this->Client->validates()) {

From 7a2568eea70802e8f99a1c81ccaf55a2bb5d8477 Mon Sep 17 00:00:00 2001
From: destinydriven 
Date: Tue, 4 Sep 2012 17:46:07 -0300
Subject: [PATCH 28/48] Updated callback signatures, visibility keywords
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Minor updates for 2.* compatibility which included fixing callback method signatures for initialize() and startup() and the addition of constructor method to handle $settings configuration array. Minor fixes to visibility keywords which should have been set as protected from previous commit.  
---
 Controller/Component/WizardComponent.php | 39 ++++++++++++++++++------
 1 file changed, 30 insertions(+), 9 deletions(-)

diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index 9584c09..f1a1c95 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -14,6 +14,14 @@
  * @license			http://www.opensource.org/licenses/mit-license.php The MIT License
  */ 
 class WizardComponent extends Component {
+	
+/**
+ * Controller $controller variable.
+ *
+ * @var string
+ * @access public
+ */
+	public $controller = null;	
 /**
  * The Component will redirect to the "expected step" after a step has been successfully
  * completed if autoAdvance is true. If false, the Component will redirect to 
@@ -117,29 +125,29 @@ class WizardComponent extends Component {
  * @var string
  * @access protected
  */
-	public $_currentStep = null;
+	protected $_currentStep = null;
 /**
  * Holds the session key for data storage.
  *
  * @var string
  * @access protected
  */
-	public $_sessionKey = null;
+	protected $_sessionKey = null;
 /**
  * Other session keys used.
  *
  * @var string
  * @access protected
  */
-	public $_configKey = null;
-	public $_branchKey = null;
+	protected $_configKey = null;
+	protected $_branchKey = null;
 /**
  * Holds the array based url for redirecting.
  * 
  * @var array
  * @access protected
  */
-	public $_wizardUrl = array();
+	protected $_wizardUrl = array();
 /**
  * Other components used.
  *
@@ -147,15 +155,28 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $components = array('Session');
+	
+/**
+ * WizardComponent Constructor
+ *
+ * @param ComponentCollection $collection A ComponentCollection this component can use to lazy-load its components
+ * @param array $settings Array of configuration settings
+ * @access public
+ */
+        public function __construct(ComponentCollection $collection, $settings = array()) {
+            parent::__construct($collection, $settings);
+            $this->_set($settings);
+        }
+
 /**
  * Initializes WizardComponent for use in the controller
  *
  * @param object $controller A reference to the instantiating controller object
  * @access public
+ * @return void
  */
-	public function initialize($controller, $settings = array()) {
+	public function initialize(Controller $controller) {
 		$this->controller = $controller;
-		$this->_set($settings);
 		
 		$this->_sessionKey	= $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name;
 		$this->_configKey 	= 'Wizard.config';
@@ -163,11 +184,11 @@ public function initialize($controller, $settings = array()) {
 	}
 /**
  * Component startup method.
- *
+ * Called after the Controller::beforeFilter() and before the controller action
  * @param object $controller A reference to the instantiating controller object
  * @access public
  */	
-	public function startup(&$controller) {		
+	public function startup(Controller $controller) {		
 		$this->steps = $this->_parseSteps($this->steps);
 		
 		$this->config('action', $this->action);

From 6663f185c94b120b915d129909920de1242002da Mon Sep 17 00:00:00 2001
From: idev247 
Date: Thu, 31 Jan 2013 09:53:33 -0500
Subject: [PATCH 29/48] Add roaming option

---
 Controller/Component/WizardComponent.php | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index f1a1c95..a475b33 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -105,12 +105,19 @@ class WizardComponent extends Component {
 	public $defaultBranch = true;
 /**
  * If true, the user will not be allowed to edit previously completed steps. They will be
- * "locked down" to the current step.
+ * "locked down" to the current step. The opposite of $roaming.
  *
  * @var boolean
  * @access public
  */	
 	public $lockdown = false;
+/**
+ * If true, the user will be allowed navigate to any steps. The opposite of $lockdown.
+ *
+ * @var boolean
+ * @access public
+ */
+	public $roaming = false;
 /**
  * If true, the component will render views found in views/{wizardAction}/{step}.ctp rather
  *  than views/{step}.ctp.
@@ -552,9 +559,11 @@ protected function _validateData() {
 		return true;
 	}
 /**
- * Validates the $step in two ways:
- *   1. Validates that the step exists in $this->steps array.
- *   2. Validates that the step is either before or exactly the expected step.
+ * Validates the $step four ways:
+ *   1. Explicitly only validate step that exists in $this->steps array.
+ *   2. If $roaming option is true any steps within $this->steps is valid
+ *   3. If $lockdown option is true only the next/current step is valid.
+ *   4. If $roaming and $lockdown is false validate the step either before or exactly the expected step.
  *
  * @param $step Step to validate.
  * @return mixed
@@ -562,7 +571,9 @@ protected function _validateData() {
  */		
 	protected function _validStep($step) {
 		if (in_array($step, $this->steps)) {
-			if ($this->lockdown) {
+			if($this->roaming) {
+				return true;
+			} elseif ($this->lockdown) {
 				return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps));
 			}
 			return (array_search($step, $this->steps) <= array_search($this->_getExpectedStep(), $this->steps));
@@ -572,4 +583,4 @@ protected function _validStep($step) {
         
 
 }
-?>
\ No newline at end of file
+?>

From 2e9cbd56f23b5b3a87118433c4d5bd87e501a391 Mon Sep 17 00:00:00 2001
From: destinydriven 
Date: Thu, 11 Apr 2013 17:36:28 -0300
Subject: [PATCH 30/48] Use NotImplementedException for missing process
 Callbacks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replace trigger_error() with more the specific NotImplementedException for missing process Callbacks
---
 Controller/Component/WizardComponent.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index a475b33..def887b 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -256,7 +256,7 @@ public function process($step) {
 					} elseif ($this->autoValidate) {
 						$proceed = $this->_validateData();
 					} else {
-						trigger_error(sprintf(__('Process Callback not found. Please create Controller::%s', true), $processCallback), E_USER_WARNING);
+						throw new NotImplementedException(sprintf(__('Process Callback not found. Please create Controller::%s', $processCallback)));
 					}
 					
 					if ($proceed) {

From 89d2619d9ae062a6ddda27353e017e3ad5818b29 Mon Sep 17 00:00:00 2001
From: Dean Sofer 
Date: Thu, 11 Apr 2013 16:14:07 -0700
Subject: [PATCH 31/48] Update README.md

---
 README.md | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/README.md b/README.md
index 56a3050..8e4cd36 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,3 @@ The Wizard plugin for CakePHP automates several aspects of multi-page forms incl
 ## Documentation
 
 Detailed documentation, including usage examples, can be found in the [GitHub wiki](http://github.com/jaredhoyt/cakephp-wizard/wiki).
-
-## Reporting issues
-
-If you have any issues with this plugin, please open a ticket on [Lighthouse](http://jaredhoyt.lighthouseapp.com/projects/60073-cakephp-wizard).

From b6cc30e718841f9175ba1b34416a3912c58fb0a4 Mon Sep 17 00:00:00 2001
From: Yevgeny Tomenko 
Date: Thu, 16 May 2013 20:48:03 +0400
Subject: [PATCH 32/48] clean code formating. implements SaveAndBack mode for
 wizard

---
 Controller/Component/WizardComponent.php | 594 +++++++++++++----------
 View/Helper/WizardHelper.php             |  86 ++--
 2 files changed, 382 insertions(+), 298 deletions(-)

diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index def887b..a28e33f 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -10,22 +10,24 @@
  *
  * Licensed under The MIT License
  *
- * @writtenby		jaredhoyt
- * @license			http://www.opensource.org/licenses/mit-license.php The MIT License
- */ 
+ * @property $Session SessionComponent
+ * @writtenby          jaredhoyt
+ * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
 class WizardComponent extends Component {
-	
+
 /**
  * Controller $controller variable.
  *
- * @var string
+ * @var Controller
  * @access public
  */
-	public $controller = null;	
+	public $controller = null;
+
 /**
  * The Component will redirect to the "expected step" after a step has been successfully
- * completed if autoAdvance is true. If false, the Component will redirect to 
- * the next step in the $steps array. (This is helpful for returning a user to 
+ * completed if autoAdvance is true. If false, the Component will redirect to
+ * the next step in the $steps array. (This is helpful for returning a user to
  * the expected step after editing a previous step w/o them having to navigate through
  * each step in between.)
  *
@@ -33,6 +35,7 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $autoAdvance = true;
+
 /**
  * Option to automatically reset if the wizard does not follow "normal"
  * operation. (ie. manual url changing, navigation away and returning, etc.)
@@ -43,6 +46,7 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $autoReset = false;
+
 /**
  * If no processCallback() exists for the current step, the component will automatically
  * validate the model data against the models included in the controller's uses array.
@@ -51,28 +55,31 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $autoValidate = false;
+
 /**
  * List of steps, in order, that are to be included in the wizard.
- *		basic example: $steps = array('contact', 'payment', 'confirm');
- * 
+ *        basic example: $steps = array('contact', 'payment', 'confirm');
+ *
  * The $steps array can also contain nested steps arrays of the same format but must be wrapped by a branch group.
- * 		plot-branched example: $steps = array('job_application', array('degree' => array('college', 'degree_type'), 'nodegree' => 'experience'), 'confirm');
+ *        plot-branched example: $steps = array('job_application', array('degree' => array('college', 'degree_type'), 'nodegree' => 'experience'), 'confirm');
  *
  * The 'branchnames' (ie 'degree', 'nodegree') are arbitrary but used as selectors for the branch() and unbranch() methods. Branches
  * can point to either another steps array or a single step. The first branch in a group that hasn't been skipped (see branch())
- * is included by default (if $defaultBranch = true). 
+ * is included by default (if $defaultBranch = true).
  *
  * @var array
  * @access public
  */
 	public $steps = array();
+
 /**
- * Controller action that processes your step. 
+ * Controller action that processes your step.
  *
  * @var string
  * @access public
  */
 	public $action = 'wizard';
+
 /**
  * Url to be redirected to after the wizard has been completed.
  * Controller::afterComplete() is called directly before redirection.
@@ -81,6 +88,7 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $completeUrl = '/';
+
 /**
  * Url to be redirected to after 'Cancel' submit button has been pressed by user.
  *
@@ -88,6 +96,7 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $cancelUrl = '/';
+
 /**
  * Url to be redirected to after 'Draft' submit button has been pressed by user.
  *
@@ -95,6 +104,7 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $draftUrl = '/';
+
 /**
  * If true, the first "non-skipped" branch in a group will be used if a branch has
  * not been included specifically.
@@ -103,14 +113,16 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $defaultBranch = true;
+
 /**
  * If true, the user will not be allowed to edit previously completed steps. They will be
  * "locked down" to the current step. The opposite of $roaming.
  *
  * @var boolean
  * @access public
- */	
+ */
 	public $lockdown = false;
+
 /**
  * If true, the user will be allowed navigate to any steps. The opposite of $lockdown.
  *
@@ -118,14 +130,24 @@ class WizardComponent extends Component {
  * @access public
  */
 	public $roaming = false;
+
 /**
  * If true, the component will render views found in views/{wizardAction}/{step}.ctp rather
  *  than views/{step}.ctp.
  *
  * @var boolean
  * @access public
- */	
+ */
 	public $nestedViews = false;
+
+/**
+* Other components used.
+*
+* @var array
+* @access public
+*/
+	public $components = array('Session');
+
 /**
  * Internal step tracking.
  *
@@ -133,6 +155,7 @@ class WizardComponent extends Component {
  * @access protected
  */
 	protected $_currentStep = null;
+
 /**
  * Holds the session key for data storage.
  *
@@ -140,6 +163,7 @@ class WizardComponent extends Component {
  * @access protected
  */
 	protected $_sessionKey = null;
+
 /**
  * Other session keys used.
  *
@@ -147,71 +171,151 @@ class WizardComponent extends Component {
  * @access protected
  */
 	protected $_configKey = null;
+
 	protected $_branchKey = null;
+
 /**
  * Holds the array based url for redirecting.
- * 
+ *
  * @var array
  * @access protected
  */
 	protected $_wizardUrl = array();
-/**
- * Other components used.
- *
- * @var array
- * @access public
- */
-	public $components = array('Session');
-	
+
 /**
  * WizardComponent Constructor
  *
  * @param ComponentCollection $collection A ComponentCollection this component can use to lazy-load its components
- * @param array $settings Array of configuration settings
+ * @param array               $settings   Array of configuration settings
+ *
  * @access public
  */
-        public function __construct(ComponentCollection $collection, $settings = array()) {
-            parent::__construct($collection, $settings);
-            $this->_set($settings);
-        }
+	public function __construct(ComponentCollection $collection, $settings = array()) {
+		parent::__construct($collection, $settings);
+		$this->_set($settings);
+	}
 
 /**
  * Initializes WizardComponent for use in the controller
  *
- * @param object $controller A reference to the instantiating controller object
+ * @param \Controller|object $controller A reference to the instantiating controller object
+ *
  * @access public
  * @return void
  */
 	public function initialize(Controller $controller) {
 		$this->controller = $controller;
-		
-		$this->_sessionKey	= $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name;
-		$this->_configKey 	= 'Wizard.config';
-		$this->_branchKey	= 'Wizard.branches.' . $controller->name;
+
+		$this->_sessionKey = $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name;
+		$this->_configKey = 'Wizard.config';
+		$this->_branchKey = 'Wizard.branches.' . $controller->name;
 	}
+
 /**
  * Component startup method.
  * Called after the Controller::beforeFilter() and before the controller action
- * @param object $controller A reference to the instantiating controller object
+ *
+ * @param \Controller|object $controller A reference to the instantiating controller object
+ *
  * @access public
- */	
-	public function startup(Controller $controller) {		
+ */
+	public function startup(Controller $controller) {
 		$this->steps = $this->_parseSteps($this->steps);
-		
+
 		$this->config('action', $this->action);
 		$this->config('steps', $this->steps);
-		
+
 		if (!in_array('Wizard.Wizard', $this->controller->helpers) && !array_key_exists('Wizard.Wizard', $this->controller->helpers)) {
 			$this->controller->helpers[] = 'Wizard.Wizard';
 		}
 	}
+
+/**
+ * Parses the steps array by stripping off nested arrays not included in the branches
+ * and returns a simple array with the correct steps.
+ *
+ * @param array $steps Array to be parsed for nested arrays and returned as simple array.
+ *
+ * @return array
+ * @access protected
+ */
+	protected function _parseSteps($steps) {
+		$parsed = array();
+
+		foreach ($steps as $key => $name) {
+			if (is_array($name)) {
+				foreach ($name as $branchName => $step) {
+					$branchType = $this->_branchType($branchName);
+
+					if ($branchType) {
+						if ($branchType !== 'skip') {
+							$branch = $branchName;
+						}
+					} elseif (empty($branch) && $this->defaultBranch) {
+						$branch = $branchName;
+					}
+				}
+
+				if (!empty($branch)) {
+					if (is_array($name[$branch])) {
+						$parsed = array_merge($parsed, $this->_parseSteps($name[$branch]));
+					} else {
+						$parsed[] = $name[$branch];
+					}
+				}
+
+				unset($branch);
+			} else {
+				$parsed[] = $name;
+			}
+		}
+		return $parsed;
+	}
+
+/**
+ * Saves configuration details for use in WizardHelper.
+ *
+ * @param $branch
+ *
+ * @return mixed
+ * @access protected
+ */
+	protected function _branchType($branch) {
+		if ($this->controller->Session->check("$this->_branchKey.$branch")) {
+			return $this->controller->Session->read("$this->_branchKey.$branch");
+		}
+		return false;
+	}
+
+/**
+ * Saves configuration details for use in WizardHelper or returns a config value.
+ * This is method usually handled only by the component.
+ *
+ * @param string $name  Name of configuration variable.
+ * @param mixed  $value Value to be stored.
+ *
+ * @return mixed
+ * @access public
+ */
+	public function config($name, $value = null) {
+		if ($value == null) {
+			// $this->controller->Session->read("$this->_configKey")
+			return $this->controller->Session->read("$this->_configKey.$name");
+		}
+		$this->controller->Session->write("$this->_configKey.$name", $value);
+		return $value;
+	}
+
 /**
  * Main Component method.
  *
  * @param string $step Name of step associated in $this->steps to be processed.
+ *
+ * @throws NotImplementedException
+ * @return bool|\CakeResponse
  * @access public
- */		
-	public function process($step) { 
+ */
+	public function process($step) {
 		if (isset($this->controller->request->data['Cancel'])) {
 			if (method_exists($this->controller, '_beforeCancel')) {
 				$this->controller->_beforeCancel($this->_getExpectedStep());
@@ -221,23 +325,30 @@ public function process($step) {
 		}
 		if (isset($this->controller->request->data['Draft'])) {
 			if (method_exists($this->controller, '_saveDraft')) {
-				$draft = array('_draft' => array('current' => array('step' => $step, 'data' => $this->controller->data)));	
+				$draft = array(
+					'_draft' => array(
+						'current' => array(
+							'step' => $step,
+							'data' => $this->controller->request->data
+						)
+					)
+				);
 				$this->controller->_saveDraft(array_merge_recursive((array)$this->read(), $draft));
 			}
-			
+
 			$this->reset();
 			$this->controller->redirect($this->draftUrl);
-		} 
-		
+		}
+
 		if (empty($step)) {
-			if ($this->controller->Session->check('Wizard.complete')) { 
+			if ($this->controller->Session->check('Wizard.complete')) {
 				if (method_exists($this->controller, '_afterComplete')) {
 					$this->controller->_afterComplete();
 				}
 				$this->reset();
 				$this->controller->redirect($this->completeUrl);
 			}
-			
+
 			$this->autoReset = false;
 		} elseif ($step == 'reset') {
 			if (!$this->lockdown) {
@@ -246,10 +357,8 @@ public function process($step) {
 		} else {
 			if ($this->_validStep($step)) {
 				$this->_setCurrentStep($step);
-												
-				if (!empty($this->controller->data) && !isset($this->controller->request->data['Previous'])) { 
-					$proceed = false;
-					
+
+				if (!empty($this->controller->data) && !isset($this->controller->request->data['Previous'])) {
 					$processCallback = '_' . Inflector::variable('process_' . $this->_currentStep);
 					if (method_exists($this->controller, $processCallback)) {
 						$proceed = $this->controller->$processCallback();
@@ -258,22 +367,25 @@ public function process($step) {
 					} else {
 						throw new NotImplementedException(sprintf(__('Process Callback not found. Please create Controller::%s', $processCallback)));
 					}
-					
+
 					if ($proceed) {
 						$this->save();
-					
+
+						if (isset($this->controller->request->data['SaveAndBack']) && prev($this->steps)) {
+							$this->redirect(current($this->steps));
+						}
 						if (next($this->steps)) {
 							if ($this->autoAdvance) {
 								$this->redirect();
 							}
 							$this->redirect(current($this->steps));
 						} else {
-							$this->controller->Session->write('Wizard.complete', $this->read());		
+							$this->controller->Session->write('Wizard.complete', $this->read());
 							$this->reset();
 							$this->controller->redirect(array('action' => $this->action));
 						}
 					}
-				} elseif (isset($this->controller->request->data['Previous']) && prev($this->steps)) { 
+				} elseif (isset($this->controller->request->data['Previous']) && prev($this->steps)) {
 					$this->redirect(current($this->steps));
 				} elseif ($this->controller->Session->check("$this->_sessionKey._draft.current")) {
 					$this->controller->data = $this->read('_draft.current.data');
@@ -281,88 +393,66 @@ public function process($step) {
 				} elseif ($this->controller->Session->check("$this->_sessionKey.$this->_currentStep")) {
 					$this->controller->data = $this->read($this->_currentStep);
 				}
-			
+
 				$prepareCallback = '_' . Inflector::variable('prepare_' . $this->_currentStep);
 				if (method_exists($this->controller, $prepareCallback)) {
 					$this->controller->$prepareCallback();
 				}
-				
+
 				$this->config('activeStep', $this->_currentStep);
-				
+
 				if ($this->nestedViews) {
 					$this->controller->viewPath .= '/' . $this->action;
 				}
-		
+
 				return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true;
 			} else {
 				$this->redirect();
 			}
 		}
-	
+
 		if ($step != 'reset' && $this->autoReset) {
 			$this->reset();
 		}
 
 		$this->redirect();
 	}
+
 /**
- * Selects a branch to be used in the steps array. The first branch in a group is included by default.
+ * Finds the first incomplete step (i.e. step data not saved in Session).
  *
- * @param string $name Branch name to be included in steps.
- * @param boolean $skip Branch will be skipped instead of included if true.
- * @access public
- */	
-	public function branch($name, $skip = false) {	
-		$branches = array();
-		
-		if ($this->controller->Session->check($this->_branchKey)) {
-			$branches = $this->controller->Session->read($this->_branchKey);
-		}
-		
-		if (isset($branches[$name])) {
-			unset($branches[$name]);
+ * @return string $step or false if complete
+ * @access protected
+ */
+	protected function _getExpectedStep() {
+		foreach ($this->steps as $step) {
+			if (!$this->controller->Session->check("$this->_sessionKey.$step")) {
+				$this->config('expectedStep', $step);
+				return $step;
+			}
 		}
-		
-		$value = $skip ? 'skip' : 'branch';
-		$branches[$name] = $value;
-		
-		$this->controller->Session->write($this->_branchKey, $branches);
+		return false;
 	}
+
 /**
- * Saves configuration details for use in WizardHelper or returns a config value. 
- * This is method usually handled only by the component.
+ * Resets the wizard by deleting the wizard session.
  *
- * @param string $name Name of configuration variable.
- * @param mixed $value Value to be stored.
- * @return mixed 
- * @access public
- */	
-	public function config($name, $value = null) {
-		if ($value == null) {
-			return $this->controller->Session->read("$this->_configKey.$name");
-		}
-		$this->controller->Session->write("$this->_configKey.$name", $value);
-	}
-/**
- * Loads previous draft session. 
- * 
- * @param array $draft Session data of same format passed to Controller::_saveDraft()
- * @see WizardComponent::process()
  * @access public
  */
-	public function loadDraft($draft = array()) {
-		if (!empty($draft['_draft']['current']['step'])) {
-			$this->restore($draft);
-			$this->redirect($draft['_draft']['current']['step']);
-		}
-		$this->redirect();
+	public function reset() {
+		$this->controller->Session->delete($this->_branchKey);
+		$this->controller->Session->delete($this->_sessionKey);
 	}
+
 /**
  * Get the data from the Session that has been stored by the WizardComponent.
  *
- * @param mixed $name The name of the session variable (or a path as sent to Set.extract)
+ * @param null $key
+ *
+ * @internal param mixed $name The name of the session variable (or a path as sent to Set.extract)
+ *
  * @return mixed The value of the session variable
- * @access public
+ * @access   public
  */
 	public function read($key = null) {
 		if ($key == null) {
@@ -372,215 +462,199 @@ public function read($key = null) {
 			return !empty($wizardData) ? $wizardData : null;
 		}
 	}
+
 /**
- * Handles Wizard redirection. A null url will redirect to the "expected" step.
+ * Validates the $step four ways:
+ *   1. Explicitly only validate step that exists in $this->steps array.
+ *   2. If $roaming option is true any steps within $this->steps is valid
+ *   3. If $lockdown option is true only the next/current step is valid.
+ *   4. If $roaming and $lockdown is false validate the step either before or exactly the expected step.
  *
- * @param string $step Stepname to be redirected to.
- * @param integer $status Optional HTTP status code (eg: 404)
- * @param boolean $exit If true, exit() will be called after the redirect
- * @see Controller::redirect()
- * @access public
+ * @param $step Step to validate.
+ *
+ * @return mixed
+ * @access protected
  */
-	public function redirect($step = null, $status = null, $exit = true) {
-		if ($step == null) {
-			$step = $this->_getExpectedStep();
+	protected function _validStep($step) {
+		if (in_array($step, $this->steps)) {
+			if ($this->roaming) {
+				return true;
+			} elseif ($this->lockdown) {
+				return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps));
+			}
+			return (array_search($step, $this->steps) <= array_search($this->_getExpectedStep(), $this->steps));
 		}
-		$url = array('controller' => $this->controller->request->controller, 'action' => $this->action, $step);
-		$this->controller->redirect($url, $status, $exit);
+		return false;
 	}
+
 /**
- * Resets the wizard by deleting the wizard session.
+ * Moves internal array pointer of $this->steps to $step and sets $this->_currentStep.
  *
- * @access public
- */	
-	public function resetWizard() {
-		$this->reset();
-	}
-/**
- * Resets the wizard by deleting the wizard session.
+ * @param string $step, Step to point to.
  *
- * @access public
- */		
-	public function reset() {
-		$this->controller->Session->delete($this->_branchKey);
-		$this->controller->Session->delete($this->_sessionKey);
+ * @access protected
+ */
+	protected function _setCurrentStep($step) {
+		$this->_currentStep = reset($this->steps);
+
+		while (current($this->steps) != $step) {
+			$this->_currentStep = next($this->steps);
+		}
 	}
+
 /**
- * Sets data into controller's wizard session. Particularly useful if the data
- * originated from WizardComponent::read() as this will restore a previous session.
- * 
- * @param array $data Data to be written to controller's wizard session.
- * @access public
+ * Validates controller data with the correct model if the model is included in
+ * the controller's uses array. This only occurs if $autoValidate = true and there
+ * is no processCallback in the controller for the current step.
+ *
+ * @return boolean
+ * @access protected
  */
-	public function restore($data = array()) {
-		$this->controller->Session->write($this->_sessionKey, $data);
+	protected function _validateData() {
+		$controller =& $this->controller;
+
+		foreach ($controller->request->data as $model => $data) {
+			if (in_array($model, $controller->uses)) {
+				$controller->{$model}->set($data);
+
+				if (!$controller->{$model}->validates()) {
+					return false;
+				}
+			}
+		}
+		return true;
 	}
+
 /**
  * Saves the data from the current step into the Session.
  *
- * Please note: This is normally called automatically by the component after 
+ * Please note: This is normally called automatically by the component after
  * a successful _processCallback, but can be called directly for advanced navigation purposes.
  *
  * @access public
- */		
+ */
 	public function save($step = null, $data = null) {
 		if (is_null($step)) {
 			$step = $this->_currentStep;
 		}
 		if (is_null($data)) {
 			$data = $this->controller->data;
-		}		
+		}
 		$this->controller->Session->write("$this->_sessionKey.$step", $data);
 	}
-	
+
 /**
- * Resets the data from the Session that has been stored by the WizardComponent.
+ * Handles Wizard redirection. A null url will redirect to the "expected" step.
+ *
+ * @param string  $step   Stepname to be redirected to.
+ * @param integer $status Optional HTTP status code (eg: 404)
+ * @param boolean $exit   If true, exit() will be called after the redirect
  *
- * @param mixed $name The name of the session variable (or a path as sent to Set.extract)
+ * @see    Controller::redirect()
  * @access public
  */
-	public function delete($key = null) {
-		if ($key == null) {
-			return;
-		} else {
-			$this->Session->delete("$this->_sessionKey.$key");
-			return;
+	public function redirect($step = null, $status = null, $exit = true) {
+		if ($step == null) {
+			$step = $this->_getExpectedStep();
 		}
+		$url = array(
+			'controller' => $this->controller->request->controller,
+			'action' => $this->action,
+			$step
+		);
+		$this->controller->redirect($url, $status, $exit);
 	}
+
 /**
- * Removes a branch from the steps array.
+ * Selects a branch to be used in the steps array. The first branch in a group is included by default.
  *
- * @param string $branch Name of branch to be removed from steps array.
- * @access public
- */	
-	public function unbranch($branch) {
-		$this->controller->Session->delete("$this->_branchKey.$branch");
-	}
-/**
- * Finds the first incomplete step (i.e. step data not saved in Session).
+ * @param string  $name Branch name to be included in steps.
+ * @param boolean $skip Branch will be skipped instead of included if true.
  *
- * @return string $step or false if complete
- * @access protected
- */	
-	protected function _getExpectedStep() {
-		foreach ($this->steps as $step) {
-			if (!$this->controller->Session->check("$this->_sessionKey.$step")) {
-				$this->config('expectedStep', $step);	
-				return $step;
-			}
+ * @access public
+ */
+	public function branch($name, $skip = false) {
+		$branches = array();
+
+		if ($this->controller->Session->check($this->_branchKey)) {
+			$branches = $this->controller->Session->read($this->_branchKey);
 		}
-		return false;
+
+		if (isset($branches[$name])) {
+			unset($branches[$name]);
+		}
+
+		$value = $skip ? 'skip' : 'branch';
+		$branches[$name] = $value;
+
+		$this->controller->Session->write($this->_branchKey, $branches);
 	}
+
 /**
- * Saves configuration details for use in WizardHelper.
+ * Loads previous draft session.
  *
- * @return mixed
- * @access protected
- */		
-	protected function _branchType($branch) {
-		if ($this->controller->Session->check("$this->_branchKey.$branch")) {
-			return $this->controller->Session->read("$this->_branchKey.$branch");
+ * @param array $draft Session data of same format passed to Controller::_saveDraft()
+ *
+ * @see    WizardComponent::process()
+ * @access public
+ */
+	public function loadDraft($draft = array()) {
+		if (!empty($draft['_draft']['current']['step'])) {
+			$this->restore($draft);
+			$this->redirect($draft['_draft']['current']['step']);
 		}
-		return false;
+		$this->redirect();
 	}
+
 /**
- * Parses the steps array by stripping off nested arrays not included in the branches
- * and returns a simple array with the correct steps. 
+ * Sets data into controller's wizard session. Particularly useful if the data
+ * originated from WizardComponent::read() as this will restore a previous session.
  *
- * @param array $steps Array to be parsed for nested arrays and returned as simple array.
- * @return array
- * @access protected
- */	
-	protected function _parseSteps($steps) {
-		$parsed = array();
-
-		foreach ($steps as $key => $name) {
-			if (is_array($name)) { 
-				foreach ($name as $branchName => $step) {
-					$branchType = $this->_branchType($branchName);
-
-					if ($branchType) {
-						if ($branchType !== 'skip') {
-							$branch = $branchName;
-						}
-					} elseif (empty($branch) && $this->defaultBranch) {
-						$branch = $branchName;
-					}
-				}
-				
-				if (!empty($branch)) {
-					if (is_array($name[$branch])) {
-						$parsed = array_merge($parsed, $this->_parseSteps($name[$branch]));
-					} else {
-						$parsed[] = $name[$branch];
-					}
-				}
-				
-				unset($branch);
-			} else {
-				$parsed[] = $name;
-			}
-		}
-		return $parsed;
+ * @param array $data Data to be written to controller's wizard session.
+ *
+ * @access public
+ */
+	public function restore($data = array()) {
+		$this->controller->Session->write($this->_sessionKey, $data);
 	}
+
 /**
- * Moves internal array pointer of $this->steps to $step and sets $this->_currentStep.
+ * Resets the wizard by deleting the wizard session.
  *
- * @param $step Step to point to.
- * @access protected
- */		
-	protected function _setCurrentStep($step) {
-		$this->_currentStep = reset($this->steps);
-		
-		while(current($this->steps) != $step) {
-			$this->_currentStep = next($this->steps);
-		}
+ * @access public
+ */
+	public function resetWizard() {
+		$this->reset();
 	}
+
 /**
- * Validates controller data with the correct model if the model is included in
- * the controller's uses array. This only occurs if $autoValidate = true and there
- * is no processCallback in the controller for the current step.
+ * Resets the data from the Session that has been stored by the WizardComponent.
  *
- * @return boolean
- * @access protected
- */	
-	protected function _validateData() {
-		$controller =& $this->controller;
-		
-		foreach ($controller->data as $model => $data) {
-			if (in_array($model, $controller->uses)) {
-				$controller->{$model}->set($data);
-				
-				if (!$controller->{$model}->validates()) {
-					return false;
-				}
-			}
+ * @param null $key
+ *
+ * @internal param mixed $name The name of the session variable (or a path as sent to Set.extract)
+ *
+ * @access   public
+ */
+	public function delete($key = null) {
+		if ($key == null) {
+			return;
+		} else {
+			$this->controller->Session->delete("$this->_sessionKey.$key");
+			return;
 		}
-		return true;
 	}
+
 /**
- * Validates the $step four ways:
- *   1. Explicitly only validate step that exists in $this->steps array.
- *   2. If $roaming option is true any steps within $this->steps is valid
- *   3. If $lockdown option is true only the next/current step is valid.
- *   4. If $roaming and $lockdown is false validate the step either before or exactly the expected step.
+ * Removes a branch from the steps array.
  *
- * @param $step Step to validate.
- * @return mixed
- * @access protected
- */		
-	protected function _validStep($step) {
-		if (in_array($step, $this->steps)) {
-			if($this->roaming) {
-				return true;
-			} elseif ($this->lockdown) {
-				return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps));
-			}
-			return (array_search($step, $this->steps) <= array_search($this->_getExpectedStep(), $this->steps));
-		}
-		return false;
+ * @param string $branch Name of branch to be removed from steps array.
+ *
+ * @access public
+ */
+	public function unbranch($branch) {
+		$this->controller->Session->delete("$this->_branchKey.$branch");
 	}
         
 
 }
-?>
diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php
index 7072d21..2afeeab 100644
--- a/View/Helper/WizardHelper.php
+++ b/View/Helper/WizardHelper.php
@@ -5,18 +5,21 @@
  * Creates links, outputs step numbers for views, and creates dynamic progress menu as the wizard is completed.
  *
  * PHP versions 4 and 5
- *
  * Comments and bug reports welcome at jaredhoyt AT gmail DOT com
- *
  * Licensed under The MIT License
  *
- * @writtenby		jaredhoyt
- * @lastmodified	Date: March 11, 2009
- * @license			http://www.opensource.org/licenses/mit-license.php The MIT License
- */ 
+ * @property FormHelper $Form
+ * @property SessionHelper $Session
+ * @property HtmlHelper $Html
+ */
 class WizardHelper extends AppHelper {
-	public $helpers = array('Session','Html','Form');
+	public $helpers = array(
+		'Session',
+		'Html',
+		'Form'
+	);
 	public $output = null;
+
 /**
  * undocumented function
  *
@@ -27,7 +30,7 @@ public function config($key = null) {
 		if ($key == null) {
 			return $this->Session->read('Wizard.config');
 		} else {
-			$wizardData = $this->Session->read('Wizard.config.'.$key);
+			$wizardData = $this->Session->read('Wizard.config.' . $key);
 			if (!empty($wizardData)) {
 				return $wizardData;
 			} else {
@@ -35,14 +38,15 @@ public function config($key = null) {
 			}
 		}
 	}
+
 /**
  * undocumented function
  *
- * @param string $title 
- * @param string $step 
- * @param string $htmlAttributes 
- * @param string $confirmMessage 
- * @param string $escapeTitle 
+ * @param string       $title
+ * @param string       $step
+ * @param array|string $htmlAttributes
+ * @param bool|string  $confirmMessage
+ * @param bool|string  $escapeTitle
  * @return string link to a specific step
  */
 	public function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
@@ -50,23 +54,24 @@ public function link($title, $step = null, $htmlAttributes = array(), $confirmMe
 			$step = $title;
 		}
 		$wizardAction = $this->config('wizardAction');
-		
-		return $this->Html->link($title, $wizardAction.$step, $htmlAttributes, $confirmMessage, $escapeTitle);
+
+		return $this->Html->link($title, $wizardAction . $step, $htmlAttributes, $confirmMessage, $escapeTitle);
 	}
+
 /**
  * Retrieve the step number of the specified step name, or the active step
  *
- * @param string $step optional name of step
- * @param string $shiftIndex optional offset of returned array index. Default 1
+ * @param string     $step       optional name of step
+ * @param int|string $shiftIndex optional offset of returned array index. Default 1
  * @return string step number. Returns false if not found
  */
 	public function stepNumber($step = null, $shiftIndex = 1) {
 		if ($step == null) {
 			$step = $this->config('activeStep');
 		}
-		
+
 		$steps = $this->config('steps');
-		
+
 		if (in_array($step, $steps)) {
 			return array_search($step, $steps) + $shiftIndex;
 		} else {
@@ -74,35 +79,35 @@ public function stepNumber($step = null, $shiftIndex = 1) {
 		}
 	}
 
-	public function stepTotal()
-	{
+	public function stepTotal() {
 		$steps = $this->config('steps');
 		return count($steps);
 	}
 
 /**
- * Returns a set of html elements containing links for each step in the wizard. 
+ * Returns a set of html elements containing links for each step in the wizard.
+ *
+ * @param array|string $titles
+ * @param array|string $attributes pass a value for 'wrap' to change the default tag used
+ * @param array|string $htmlAttributes
+ * @param bool|string  $confirmMessage
+ * @param bool|string  $escapeTitle
  *
- * @param string $titles 
- * @param string $attributes pass a value for 'wrap' to change the default tag used
- * @param string $htmlAttributes 
- * @param string $confirmMessage 
- * @param string $escapeTitle 
  * @return string
  */
 	public function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
 		$wizardConfig = $this->config();
-		extract($wizardConfig);	
-                $wizardAction = $this->config('wizardAction');
+		extract($wizardConfig);
+		$wizardAction = $this->config('wizardAction');
 
 		$attributes = array_merge(array('wrap' => 'div'), $attributes);
 		extract($attributes);
-		
+
 		$incomplete = null;
 
 		foreach ($steps as $title => $step) {
 			$title = empty($titles[$step]) ? $step : $titles[$step];
-			
+
 			if (!$incomplete) {
 				if ($step == $expectedStep) {
 					$incomplete = true;
@@ -113,25 +118,30 @@ public function progressMenu($titles = array(), $attributes = array(), $htmlAttr
 				if ($step == $activeStep) {
 					$class .= ' active';
 				}
-				$this->output .= "<$wrap class='$class'>" . $this->Html->link($title, array('action' => $wizardAction, $step), $htmlAttributes, $confirmMessage, $escapeTitle) . "";
+				$this->output .= "<$wrap class='$class'>" . $this->Html->link($title, array(
+						'action' => $wizardAction,
+						$step
+					), $htmlAttributes, $confirmMessage, $escapeTitle) . "";
 			} else {
 				$this->output .= "<$wrap class='incomplete'>" . $title . "";
 			}
 		}
-		
+
 		return $this->output;
 	}
+
 /**
  * Wrapper for Form->create()
  *
- * @param string $model 
- * @param array $options 
+ * @param string $model
+ * @param array  $options
+ *
  * @return string
  */
 	public function create($model = null, $options = array()) {
-		if (!isset($options['url']) || !in_array($this->params['pass'][0], $options['url']))
-			$options['url'][] = $this->params['pass'][0];
+		if (!isset($options['url']) || !in_array($this->request->params['pass'][0], $options['url'])) {
+			$options['url'][] = $this->request->params['pass'][0];
+		}
 		return $this->Form->create($model, $options);
 	}
 }
-?>
\ No newline at end of file

From ae628e950b2753cc7accf750bed546f778250eb8 Mon Sep 17 00:00:00 2001
From: Yevgeny Tomenko 
Date: Tue, 21 May 2013 18:43:18 +0400
Subject: [PATCH 33/48] fix cake2 style

---
 Controller/Component/WizardComponent.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index a28e33f..54aa152 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -538,7 +538,7 @@ public function save($step = null, $data = null) {
 			$step = $this->_currentStep;
 		}
 		if (is_null($data)) {
-			$data = $this->controller->data;
+			$data = $this->controller->request->data;
 		}
 		$this->controller->Session->write("$this->_sessionKey.$step", $data);
 	}

From 42e3d7a0c44dc3134bce67e6b910f77f9039071d Mon Sep 17 00:00:00 2001
From: Lucas Freitas 
Date: Sat, 15 Feb 2014 12:52:26 -0200
Subject: [PATCH 34/48] Adding initial composer.json file

---
 composer.json | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 composer.json

diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..845016c
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,26 @@
+{
+    "name": "lucasff/CakePHP-Wizard",
+    "description": "CakePHP Wizard Plugin",
+    "type": "cakephp-plugin",
+    "keywords": ["cakephp", "wizard"],
+    "homepage": "https://github.com/ProLoser/CakePHP-Wizard",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Dean Sofer",
+            "homepage": "https://github.com/ProLoser",
+            "role": "developer"
+        }
+    ],
+    "replace": {
+        "lucasff/CakePHP-Wizard": "dev-master",
+        "ProLoser/CakePHP-Wizard": "dev-master"
+    },
+    "require": {
+        "php": ">=5.3.0",
+        "composer/installers": "*"
+    },
+    "extra": {
+        "installer-name": "Wizard"
+    }
+}

From d47f6b2d28d9a270dead777677af1e3aca29afcd Mon Sep 17 00:00:00 2001
From: Lucas Freitas 
Date: Sat, 15 Feb 2014 12:55:01 -0200
Subject: [PATCH 35/48] Packagist requires lowercase package names

---
 composer.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/composer.json b/composer.json
index 845016c..825f8c7 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
 {
-    "name": "lucasff/CakePHP-Wizard",
+    "name": "lucasff/cakephp-wizard",
     "description": "CakePHP Wizard Plugin",
     "type": "cakephp-plugin",
     "keywords": ["cakephp", "wizard"],
@@ -13,7 +13,7 @@
         }
     ],
     "replace": {
-        "lucasff/CakePHP-Wizard": "dev-master",
+        "lucasff/cakephp-wizard": "dev-master",
         "ProLoser/CakePHP-Wizard": "dev-master"
     },
     "require": {

From 5be2e212155b75b22e0696331af8b6c4c8b62005 Mon Sep 17 00:00:00 2001
From: Lucas Freitas 
Date: Mon, 24 Feb 2014 01:13:31 -0300
Subject: [PATCH 36/48] Code reformatting and PHPDoc fixing

---
 Controller/Component/WizardComponent.php | 1207 ++++++++++++----------
 1 file changed, 638 insertions(+), 569 deletions(-)

diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index def887b..5256a98 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -1,4 +1,5 @@
  array('college', 'degree_type'), 'nodegree' => 'experience'), 'confirm');
- *
- * The 'branchnames' (ie 'degree', 'nodegree') are arbitrary but used as selectors for the branch() and unbranch() methods. Branches
- * can point to either another steps array or a single step. The first branch in a group that hasn't been skipped (see branch())
- * is included by default (if $defaultBranch = true). 
- *
- * @var array
- * @access public
- */
-	public $steps = array();
-/**
- * Controller action that processes your step. 
- *
- * @var string
- * @access public
- */
-	public $action = 'wizard';
-/**
- * Url to be redirected to after the wizard has been completed.
- * Controller::afterComplete() is called directly before redirection.
- *
- * @var mixed
- * @access public
- */
-	public $completeUrl = '/';
-/**
- * Url to be redirected to after 'Cancel' submit button has been pressed by user.
- *
- * @var mixed
- * @access public
- */
-	public $cancelUrl = '/';
-/**
- * Url to be redirected to after 'Draft' submit button has been pressed by user.
- *
- * @var mixed
- * @access public
- */
-	public $draftUrl = '/';
-/**
- * If true, the first "non-skipped" branch in a group will be used if a branch has
- * not been included specifically.
- *
- * @var boolean
- * @access public
- */
-	public $defaultBranch = true;
-/**
- * If true, the user will not be allowed to edit previously completed steps. They will be
- * "locked down" to the current step. The opposite of $roaming.
- *
- * @var boolean
- * @access public
- */	
-	public $lockdown = false;
-/**
- * If true, the user will be allowed navigate to any steps. The opposite of $lockdown.
- *
- * @var boolean
- * @access public
- */
-	public $roaming = false;
-/**
- * If true, the component will render views found in views/{wizardAction}/{step}.ctp rather
- *  than views/{step}.ctp.
- *
- * @var boolean
- * @access public
- */	
-	public $nestedViews = false;
-/**
- * Internal step tracking.
- *
- * @var string
- * @access protected
- */
-	protected $_currentStep = null;
-/**
- * Holds the session key for data storage.
- *
- * @var string
- * @access protected
+ * @writtenby          jaredhoyt
+ * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
  */
-	protected $_sessionKey = null;
-/**
- * Other session keys used.
- *
- * @var string
- * @access protected
- */
-	protected $_configKey = null;
-	protected $_branchKey = null;
-/**
- * Holds the array based url for redirecting.
- * 
- * @var array
- * @access protected
- */
-	protected $_wizardUrl = array();
-/**
- * Other components used.
- *
- * @var array
- * @access public
- */
-	public $components = array('Session');
-	
-/**
- * WizardComponent Constructor
- *
- * @param ComponentCollection $collection A ComponentCollection this component can use to lazy-load its components
- * @param array $settings Array of configuration settings
- * @access public
- */
-        public function __construct(ComponentCollection $collection, $settings = array()) {
-            parent::__construct($collection, $settings);
-            $this->_set($settings);
+class WizardComponent extends Component
+{
+
+    /**
+     * Controller $controller variable.
+     *
+     * @var Controller
+     * @access public
+     */
+    public $controller = null;
+    /**
+     * The Component will redirect to the "expected step" after a step has been successfully
+     * completed if autoAdvance is true. If false, the Component will redirect to
+     * the next step in the $steps array. (This is helpful for returning a user to
+     * the expected step after editing a previous step w/o them having to navigate through
+     * each step in between.)
+     *
+     * @var boolean
+     * @access public
+     */
+    public $autoAdvance = true;
+    /**
+     * Option to automatically reset if the wizard does not follow "normal"
+     * operation. (ie. manual url changing, navigation away and returning, etc.)
+     * Set this to false if you want the Wizard to return to the "expected step"
+     * after invalid navigation.
+     *
+     * @var boolean
+     * @access public
+     */
+    public $autoReset = false;
+    /**
+     * If no processCallback() exists for the current step, the component will automatically
+     * validate the model data against the models included in the controller's uses array.
+     *
+     * @var boolean
+     * @access public
+     */
+    public $autoValidate = false;
+    /**
+     * List of steps, in order, that are to be included in the wizard.
+     *        basic example: $steps = array('contact', 'payment', 'confirm');
+     *
+     * The $steps array can also contain nested steps arrays of the same format but must be wrapped by a branch group.
+     *        plot-branched example: $steps = array('job_application', array('degree' => array('college', 'degree_type'), 'nodegree' => 'experience'), 'confirm');
+     *
+     * The 'branchnames' (ie 'degree', 'nodegree') are arbitrary but used as selectors for the branch() and unbranch() methods. Branches
+     * can point to either another steps array or a single step. The first branch in a group that hasn't been skipped (see branch())
+     * is included by default (if $defaultBranch = true).
+     *
+     * @var array
+     * @access public
+     */
+    public $steps = array();
+    /**
+     * Controller action that processes your step.
+     *
+     * @var string
+     * @access public
+     */
+    public $action = 'wizard';
+    /**
+     * Url to be redirected to after the wizard has been completed.
+     * Controller::afterComplete() is called directly before redirection.
+     *
+     * @var mixed
+     * @access public
+     */
+    public $completeUrl = '/';
+    /**
+     * Url to be redirected to after 'Cancel' submit button has been pressed by user.
+     *
+     * @var mixed
+     * @access public
+     */
+    public $cancelUrl = '/';
+    /**
+     * Url to be redirected to after 'Draft' submit button has been pressed by user.
+     *
+     * @var mixed
+     * @access public
+     */
+    public $draftUrl = '/';
+    /**
+     * If true, the first "non-skipped" branch in a group will be used if a branch has
+     * not been included specifically.
+     *
+     * @var boolean
+     * @access public
+     */
+    public $defaultBranch = true;
+    /**
+     * If true, the user will not be allowed to edit previously completed steps. They will be
+     * "locked down" to the current step. The opposite of $roaming.
+     *
+     * @var boolean
+     * @access public
+     */
+    public $lockdown = false;
+    /**
+     * If true, the user will be allowed navigate to any steps. The opposite of $lockdown.
+     *
+     * @var boolean
+     * @access public
+     */
+    public $roaming = false;
+    /**
+     * If true, the component will render views found in views/{wizardAction}/{step}.ctp rather
+     *  than views/{step}.ctp.
+     *
+     * @var boolean
+     * @access public
+     */
+    public $nestedViews = false;
+    /**
+     * Internal step tracking.
+     *
+     * @var string
+     * @access protected
+     */
+    protected $_currentStep = null;
+    /**
+     * Holds the session key for data storage.
+     *
+     * @var string
+     * @access protected
+     */
+    protected $_sessionKey = null;
+    /**
+     * Other session keys used.
+     *
+     * @var string
+     * @access protected
+     */
+    protected $_configKey = null;
+    protected $_branchKey = null;
+    /**
+     * Holds the array based url for redirecting.
+     *
+     * @var array
+     * @access protected
+     */
+    protected $_wizardUrl = array();
+    /**
+     * Other components used.
+     *
+     * @var array
+     * @access public
+     */
+    public $components = array('Session');
+
+    /**
+     * WizardComponent Constructor
+     *
+     * @param ComponentCollection $collection A ComponentCollection this component can use to lazy-load its components
+     * @param array               $settings   Array of configuration settings
+     *
+     * @access public
+     */
+    public function __construct(ComponentCollection $collection, $settings = array())
+    {
+        parent::__construct($collection, $settings);
+        $this->_set($settings);
+    }
+
+    /**
+     * Initializes WizardComponent for use in the controller
+     *
+     * @param Controller $controller A reference to the instantiating controller object
+     *
+     * @access public
+     * @return void
+     */
+    public function initialize(Controller $controller)
+    {
+        $this->controller = $controller;
+
+        $this->_sessionKey = $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name;
+        $this->_configKey  = 'Wizard.config';
+        $this->_branchKey  = 'Wizard.branches.' . $controller->name;
+    }
+
+    /**
+     * Component startup method.
+     * Called after the Controller::beforeFilter() and before the controller action
+     *
+     * @param Controller $controller A reference to the instantiating controller object
+     *
+     * @access public
+     */
+    public function startup(Controller $controller)
+    {
+        $this->steps = $this->_parseSteps($this->steps);
+
+        $this->config('action', $this->action);
+        $this->config('steps', $this->steps);
+
+        if (!in_array('Wizard.Wizard', $this->controller->helpers) && !array_key_exists('Wizard.Wizard', $this->controller->helpers)) {
+            $this->controller->helpers[] = 'Wizard.Wizard';
         }
+    }
+
+    /**
+     * Main Component method.
+     *
+     * @param string $step Name of step associated in $this->steps to be processed.
+     *
+     * @access public
+     * @return mixed
+     * @throws NotImplementedException
+     */
+    public function process($step)
+    {
+        if (isset($this->controller->request->data['Cancel'])) {
+            if (method_exists($this->controller, '_beforeCancel')) {
+                $this->controller->_beforeCancel($this->_getExpectedStep());
+            }
+            $this->reset();
+            $this->controller->redirect($this->cancelUrl);
+        }
+        if (isset($this->controller->request->data['Draft'])) {
+            if (method_exists($this->controller, '_saveDraft')) {
+                $draft = array('_draft' => array('current' => array('step' => $step, 'data' => $this->controller->data)));
+                $this->controller->_saveDraft(array_merge_recursive((array)$this->read(), $draft));
+            }
+
+            $this->reset();
+            $this->controller->redirect($this->draftUrl);
+        }
+
+        if (empty($step)) {
+            if ($this->controller->Session->check('Wizard.complete')) {
+                if (method_exists($this->controller, '_afterComplete')) {
+                    $this->controller->_afterComplete();
+                }
+                $this->reset();
+                $this->controller->redirect($this->completeUrl);
+            }
+
+            $this->autoReset = false;
+        } elseif ($step == 'reset') {
+            if (!$this->lockdown) {
+                $this->reset();
+            }
+        } else {
+            if ($this->_validStep($step)) {
+                $this->_setCurrentStep($step);
+
+                if (!empty($this->controller->data) && !isset($this->controller->request->data['Previous'])) {
+                    $proceed = false;
+
+                    $processCallback = '_' . Inflector::variable('process_' . $this->_currentStep);
+                    if (method_exists($this->controller, $processCallback)) {
+                        $proceed = $this->controller->$processCallback();
+                    } elseif ($this->autoValidate) {
+                        $proceed = $this->_validateData();
+                    } else {
+                        throw new NotImplementedException(sprintf(__('Process Callback not found. Please create Controller::%s', $processCallback)));
+                    }
+
+                    if ($proceed) {
+                        $this->save();
+
+                        if (next($this->steps)) {
+                            if ($this->autoAdvance) {
+                                $this->redirect();
+                            }
+                            $this->redirect(current($this->steps));
+                        } else {
+                            $this->controller->Session->write('Wizard.complete', $this->read());
+                            $this->reset();
+                            $this->controller->redirect(array('action' => $this->action));
+                        }
+                    }
+                } elseif (isset($this->controller->request->data['Previous']) && prev($this->steps)) {
+                    $this->redirect(current($this->steps));
+                } elseif ($this->controller->Session->check("$this->_sessionKey._draft.current")) {
+                    $this->controller->data = $this->read('_draft.current.data');
+                    $this->controller->Session->delete("$this->_sessionKey._draft.current");
+                } elseif ($this->controller->Session->check("$this->_sessionKey.$this->_currentStep")) {
+                    $this->controller->data = $this->read($this->_currentStep);
+                }
+
+                $prepareCallback = '_' . Inflector::variable('prepare_' . $this->_currentStep);
+                if (method_exists($this->controller, $prepareCallback)) {
+                    $this->controller->$prepareCallback();
+                }
+
+                $this->config('activeStep', $this->_currentStep);
+
+                if ($this->nestedViews) {
+                    $this->controller->viewPath .= '/' . $this->action;
+                }
+
+                return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true;
+            } else {
+                $this->redirect();
+            }
+        }
+
+        if ($step != 'reset' && $this->autoReset) {
+            $this->reset();
+        }
+
+        $this->redirect();
+    }
+
+    /**
+     * Selects a branch to be used in the steps array. The first branch in a group is included by default.
+     *
+     * @param string  $name Branch name to be included in steps.
+     * @param boolean $skip Branch will be skipped instead of included if true.
+     *
+     * @access public
+     */
+    public function branch($name, $skip = false)
+    {
+        $branches = array();
+
+        if ($this->controller->Session->check($this->_branchKey)) {
+            $branches = $this->controller->Session->read($this->_branchKey);
+        }
+
+        if (isset($branches[$name])) {
+            unset($branches[$name]);
+        }
+
+        $value           = $skip ? 'skip' : 'branch';
+        $branches[$name] = $value;
+
+        $this->controller->Session->write($this->_branchKey, $branches);
+    }
+
+    /**
+     * Saves configuration details for use in WizardHelper or returns a config value.
+     * This is method usually handled only by the component.
+     *
+     * @param string $name  Name of configuration variable.
+     * @param mixed  $value Value to be stored.
+     *
+     * @return mixed
+     * @access public
+     */
+    public function config($name, $value = null)
+    {
+        if ($value == null) {
+            return $this->controller->Session->read("$this->_configKey.$name");
+        }
+        $this->controller->Session->write("$this->_configKey.$name", $value);
+    }
+
+    /**
+     * Loads previous draft session.
+     *
+     * @param array $draft Session data of same format passed to Controller::_saveDraft()
+     *
+     * @see    WizardComponent::process()
+     * @access public
+     */
+    public function loadDraft($draft = array())
+    {
+        if (!empty($draft['_draft']['current']['step'])) {
+            $this->restore($draft);
+            $this->redirect($draft['_draft']['current']['step']);
+        }
+        $this->redirect();
+    }
+
+    /**
+     * Get the data from the Session that has been stored by the WizardComponent.
+     *
+     * @param mixed $key The name of the session variable (or a path as sent to Set.extract)
+     *
+     * @return mixed The value of the session variable
+     * @access public
+     */
+    public function read($key = null)
+    {
+        if ($key == null) {
+            return $this->controller->Session->read($this->_sessionKey);
+        } else {
+            $wizardData = $this->controller->Session->read("$this->_sessionKey.$key");
+
+            return !empty($wizardData) ? $wizardData : null;
+        }
+    }
+
+    /**
+     * Handles Wizard redirection. A null url will redirect to the "expected" step.
+     *
+     * @param string  $step   Stepname to be redirected to.
+     * @param integer $status Optional HTTP status code (eg: 404)
+     * @param boolean $exit   If true, exit() will be called after the redirect
+     *
+     * @see    Controller::redirect()
+     * @access public
+     */
+    public function redirect($step = null, $status = null, $exit = true)
+    {
+        if ($step == null) {
+            $step = $this->_getExpectedStep();
+        }
+        $url = array('controller' => $this->controller->request->controller, 'action' => $this->action, $step);
+        $this->controller->redirect($url, $status, $exit);
+    }
+
+    /**
+     * Resets the wizard by deleting the wizard session.
+     *
+     * @access public
+     */
+    public function resetWizard()
+    {
+        $this->reset();
+    }
+
+    /**
+     * Resets the wizard by deleting the wizard session.
+     *
+     * @access public
+     */
+    public function reset()
+    {
+        $this->controller->Session->delete($this->_branchKey);
+        $this->controller->Session->delete($this->_sessionKey);
+    }
+
+    /**
+     * Sets data into controller's wizard session. Particularly useful if the data
+     * originated from WizardComponent::read() as this will restore a previous session.
+     *
+     * @param array $data Data to be written to controller's wizard session.
+     *
+     * @access public
+     */
+    public function restore($data = array())
+    {
+        $this->controller->Session->write($this->_sessionKey, $data);
+    }
+
+    /**
+     * Saves the data from the current step into the Session.
+     *
+     * Please note: This is normally called automatically by the component after
+     * a successful _processCallback, but can be called directly for advanced navigation purposes.
+     *
+     * @access public
+     */
+    public function save($step = null, $data = null)
+    {
+        if (is_null($step)) {
+            $step = $this->_currentStep;
+        }
+        if (is_null($data)) {
+            $data = $this->controller->data;
+        }
+        $this->controller->Session->write("$this->_sessionKey.$step", $data);
+    }
+
+    /**
+     * Resets the data from the Session that has been stored by the WizardComponent.
+     *
+     * @param mixed $key The name of the session variable (or a path as sent to Set.extract)
+     *
+     * @access public
+     */
+    public function delete($key = null)
+    {
+        if ($key == null) {
+            return;
+        } else {
+            $this->Session->delete("$this->_sessionKey.$key");
+
+            return;
+        }
+    }
+
+    /**
+     * Removes a branch from the steps array.
+     *
+     * @param string $branch Name of branch to be removed from steps array.
+     *
+     * @access public
+     */
+    public function unbranch($branch)
+    {
+        $this->controller->Session->delete("$this->_branchKey.$branch");
+    }
+
+    /**
+     * Finds the first incomplete step (i.e. step data not saved in Session).
+     *
+     * @return string $step or false if complete
+     * @access protected
+     */
+    protected function _getExpectedStep()
+    {
+        foreach ($this->steps as $step) {
+            if (!$this->controller->Session->check("$this->_sessionKey.$step")) {
+                $this->config('expectedStep', $step);
+
+                return $step;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Saves configuration details for use in WizardHelper.
+     *
+     * @param mixed $branch
+     *
+     * @return mixed
+     * @access protected
+     */
+    protected function _branchType($branch)
+    {
+        if ($this->controller->Session->check("$this->_branchKey.$branch")) {
+            return $this->controller->Session->read("$this->_branchKey.$branch");
+        }
+
+        return false;
+    }
+
+    /**
+     * Parses the steps array by stripping off nested arrays not included in the branches
+     * and returns a simple array with the correct steps.
+     *
+     * @param array $steps Array to be parsed for nested arrays and returned as simple array.
+     *
+     * @return array
+     * @access protected
+     */
+    protected function _parseSteps($steps)
+    {
+        $parsed = array();
+
+        foreach ($steps as $key => $name) {
+            if (is_array($name)) {
+                foreach ($name as $branchName => $step) {
+                    $branchType = $this->_branchType($branchName);
+
+                    if ($branchType) {
+                        if ($branchType !== 'skip') {
+                            $branch = $branchName;
+                        }
+                    } elseif (empty($branch) && $this->defaultBranch) {
+                        $branch = $branchName;
+                    }
+                }
+
+                if (!empty($branch)) {
+                    if (is_array($name[$branch])) {
+                        $parsed = array_merge($parsed, $this->_parseSteps($name[$branch]));
+                    } else {
+                        $parsed[] = $name[$branch];
+                    }
+                }
+
+                unset($branch);
+            } else {
+                $parsed[] = $name;
+            }
+        }
+
+        return $parsed;
+    }
+
+    /**
+     * Moves internal array pointer of $this->steps to $step and sets $this->_currentStep.
+     *
+     * @param string $step Step to point to.
+     *
+     * @access protected
+     */
+    protected function _setCurrentStep($step)
+    {
+        $this->_currentStep = reset($this->steps);
+
+        while (current($this->steps) != $step) {
+            $this->_currentStep = next($this->steps);
+        }
+    }
+
+    /**
+     * Validates controller data with the correct model if the model is included in
+     * the controller's uses array. This only occurs if $autoValidate = true and there
+     * is no processCallback in the controller for the current step.
+     *
+     * @return boolean
+     * @access protected
+     */
+    protected function _validateData()
+    {
+        $controller =& $this->controller;
+
+        foreach ($controller->data as $model => $data) {
+            if (in_array($model, $controller->uses)) {
+                $controller->{$model}->set($data);
+
+                if (!$controller->{$model}->validates()) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Validates the $step four ways:
+     *   1. Explicitly only validate step that exists in $this->steps array.
+     *   2. If $roaming option is true any steps within $this->steps is valid
+     *   3. If $lockdown option is true only the next/current step is valid.
+     *   4. If $roaming and $lockdown is false validate the step either before or exactly the expected step.
+     *
+     * @param string $step Step to validate.
+     *
+     * @return mixed
+     * @access protected
+     */
+    protected function _validStep($step)
+    {
+        if (in_array($step, $this->steps)) {
+            if ($this->roaming) {
+                return true;
+            } elseif ($this->lockdown) {
+                return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps));
+            }
+
+            return (array_search($step, $this->steps) <= array_search($this->_getExpectedStep(), $this->steps));
+        }
+
+        return false;
+    }
 
-/**
- * Initializes WizardComponent for use in the controller
- *
- * @param object $controller A reference to the instantiating controller object
- * @access public
- * @return void
- */
-	public function initialize(Controller $controller) {
-		$this->controller = $controller;
-		
-		$this->_sessionKey	= $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name;
-		$this->_configKey 	= 'Wizard.config';
-		$this->_branchKey	= 'Wizard.branches.' . $controller->name;
-	}
-/**
- * Component startup method.
- * Called after the Controller::beforeFilter() and before the controller action
- * @param object $controller A reference to the instantiating controller object
- * @access public
- */	
-	public function startup(Controller $controller) {		
-		$this->steps = $this->_parseSteps($this->steps);
-		
-		$this->config('action', $this->action);
-		$this->config('steps', $this->steps);
-		
-		if (!in_array('Wizard.Wizard', $this->controller->helpers) && !array_key_exists('Wizard.Wizard', $this->controller->helpers)) {
-			$this->controller->helpers[] = 'Wizard.Wizard';
-		}
-	}
-/**
- * Main Component method.
- *
- * @param string $step Name of step associated in $this->steps to be processed.
- * @access public
- */		
-	public function process($step) { 
-		if (isset($this->controller->request->data['Cancel'])) {
-			if (method_exists($this->controller, '_beforeCancel')) {
-				$this->controller->_beforeCancel($this->_getExpectedStep());
-			}
-			$this->reset();
-			$this->controller->redirect($this->cancelUrl);
-		}
-		if (isset($this->controller->request->data['Draft'])) {
-			if (method_exists($this->controller, '_saveDraft')) {
-				$draft = array('_draft' => array('current' => array('step' => $step, 'data' => $this->controller->data)));	
-				$this->controller->_saveDraft(array_merge_recursive((array)$this->read(), $draft));
-			}
-			
-			$this->reset();
-			$this->controller->redirect($this->draftUrl);
-		} 
-		
-		if (empty($step)) {
-			if ($this->controller->Session->check('Wizard.complete')) { 
-				if (method_exists($this->controller, '_afterComplete')) {
-					$this->controller->_afterComplete();
-				}
-				$this->reset();
-				$this->controller->redirect($this->completeUrl);
-			}
-			
-			$this->autoReset = false;
-		} elseif ($step == 'reset') {
-			if (!$this->lockdown) {
-				$this->reset();
-			}
-		} else {
-			if ($this->_validStep($step)) {
-				$this->_setCurrentStep($step);
-												
-				if (!empty($this->controller->data) && !isset($this->controller->request->data['Previous'])) { 
-					$proceed = false;
-					
-					$processCallback = '_' . Inflector::variable('process_' . $this->_currentStep);
-					if (method_exists($this->controller, $processCallback)) {
-						$proceed = $this->controller->$processCallback();
-					} elseif ($this->autoValidate) {
-						$proceed = $this->_validateData();
-					} else {
-						throw new NotImplementedException(sprintf(__('Process Callback not found. Please create Controller::%s', $processCallback)));
-					}
-					
-					if ($proceed) {
-						$this->save();
-					
-						if (next($this->steps)) {
-							if ($this->autoAdvance) {
-								$this->redirect();
-							}
-							$this->redirect(current($this->steps));
-						} else {
-							$this->controller->Session->write('Wizard.complete', $this->read());		
-							$this->reset();
-							$this->controller->redirect(array('action' => $this->action));
-						}
-					}
-				} elseif (isset($this->controller->request->data['Previous']) && prev($this->steps)) { 
-					$this->redirect(current($this->steps));
-				} elseif ($this->controller->Session->check("$this->_sessionKey._draft.current")) {
-					$this->controller->data = $this->read('_draft.current.data');
-					$this->controller->Session->delete("$this->_sessionKey._draft.current");
-				} elseif ($this->controller->Session->check("$this->_sessionKey.$this->_currentStep")) {
-					$this->controller->data = $this->read($this->_currentStep);
-				}
-			
-				$prepareCallback = '_' . Inflector::variable('prepare_' . $this->_currentStep);
-				if (method_exists($this->controller, $prepareCallback)) {
-					$this->controller->$prepareCallback();
-				}
-				
-				$this->config('activeStep', $this->_currentStep);
-				
-				if ($this->nestedViews) {
-					$this->controller->viewPath .= '/' . $this->action;
-				}
-		
-				return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true;
-			} else {
-				$this->redirect();
-			}
-		}
-	
-		if ($step != 'reset' && $this->autoReset) {
-			$this->reset();
-		}
-
-		$this->redirect();
-	}
-/**
- * Selects a branch to be used in the steps array. The first branch in a group is included by default.
- *
- * @param string $name Branch name to be included in steps.
- * @param boolean $skip Branch will be skipped instead of included if true.
- * @access public
- */	
-	public function branch($name, $skip = false) {	
-		$branches = array();
-		
-		if ($this->controller->Session->check($this->_branchKey)) {
-			$branches = $this->controller->Session->read($this->_branchKey);
-		}
-		
-		if (isset($branches[$name])) {
-			unset($branches[$name]);
-		}
-		
-		$value = $skip ? 'skip' : 'branch';
-		$branches[$name] = $value;
-		
-		$this->controller->Session->write($this->_branchKey, $branches);
-	}
-/**
- * Saves configuration details for use in WizardHelper or returns a config value. 
- * This is method usually handled only by the component.
- *
- * @param string $name Name of configuration variable.
- * @param mixed $value Value to be stored.
- * @return mixed 
- * @access public
- */	
-	public function config($name, $value = null) {
-		if ($value == null) {
-			return $this->controller->Session->read("$this->_configKey.$name");
-		}
-		$this->controller->Session->write("$this->_configKey.$name", $value);
-	}
-/**
- * Loads previous draft session. 
- * 
- * @param array $draft Session data of same format passed to Controller::_saveDraft()
- * @see WizardComponent::process()
- * @access public
- */
-	public function loadDraft($draft = array()) {
-		if (!empty($draft['_draft']['current']['step'])) {
-			$this->restore($draft);
-			$this->redirect($draft['_draft']['current']['step']);
-		}
-		$this->redirect();
-	}
-/**
- * Get the data from the Session that has been stored by the WizardComponent.
- *
- * @param mixed $name The name of the session variable (or a path as sent to Set.extract)
- * @return mixed The value of the session variable
- * @access public
- */
-	public function read($key = null) {
-		if ($key == null) {
-			return $this->controller->Session->read($this->_sessionKey);
-		} else {
-			$wizardData = $this->controller->Session->read("$this->_sessionKey.$key");
-			return !empty($wizardData) ? $wizardData : null;
-		}
-	}
-/**
- * Handles Wizard redirection. A null url will redirect to the "expected" step.
- *
- * @param string $step Stepname to be redirected to.
- * @param integer $status Optional HTTP status code (eg: 404)
- * @param boolean $exit If true, exit() will be called after the redirect
- * @see Controller::redirect()
- * @access public
- */
-	public function redirect($step = null, $status = null, $exit = true) {
-		if ($step == null) {
-			$step = $this->_getExpectedStep();
-		}
-		$url = array('controller' => $this->controller->request->controller, 'action' => $this->action, $step);
-		$this->controller->redirect($url, $status, $exit);
-	}
-/**
- * Resets the wizard by deleting the wizard session.
- *
- * @access public
- */	
-	public function resetWizard() {
-		$this->reset();
-	}
-/**
- * Resets the wizard by deleting the wizard session.
- *
- * @access public
- */		
-	public function reset() {
-		$this->controller->Session->delete($this->_branchKey);
-		$this->controller->Session->delete($this->_sessionKey);
-	}
-/**
- * Sets data into controller's wizard session. Particularly useful if the data
- * originated from WizardComponent::read() as this will restore a previous session.
- * 
- * @param array $data Data to be written to controller's wizard session.
- * @access public
- */
-	public function restore($data = array()) {
-		$this->controller->Session->write($this->_sessionKey, $data);
-	}
-/**
- * Saves the data from the current step into the Session.
- *
- * Please note: This is normally called automatically by the component after 
- * a successful _processCallback, but can be called directly for advanced navigation purposes.
- *
- * @access public
- */		
-	public function save($step = null, $data = null) {
-		if (is_null($step)) {
-			$step = $this->_currentStep;
-		}
-		if (is_null($data)) {
-			$data = $this->controller->data;
-		}		
-		$this->controller->Session->write("$this->_sessionKey.$step", $data);
-	}
-	
-/**
- * Resets the data from the Session that has been stored by the WizardComponent.
- *
- * @param mixed $name The name of the session variable (or a path as sent to Set.extract)
- * @access public
- */
-	public function delete($key = null) {
-		if ($key == null) {
-			return;
-		} else {
-			$this->Session->delete("$this->_sessionKey.$key");
-			return;
-		}
-	}
-/**
- * Removes a branch from the steps array.
- *
- * @param string $branch Name of branch to be removed from steps array.
- * @access public
- */	
-	public function unbranch($branch) {
-		$this->controller->Session->delete("$this->_branchKey.$branch");
-	}
-/**
- * Finds the first incomplete step (i.e. step data not saved in Session).
- *
- * @return string $step or false if complete
- * @access protected
- */	
-	protected function _getExpectedStep() {
-		foreach ($this->steps as $step) {
-			if (!$this->controller->Session->check("$this->_sessionKey.$step")) {
-				$this->config('expectedStep', $step);	
-				return $step;
-			}
-		}
-		return false;
-	}
-/**
- * Saves configuration details for use in WizardHelper.
- *
- * @return mixed
- * @access protected
- */		
-	protected function _branchType($branch) {
-		if ($this->controller->Session->check("$this->_branchKey.$branch")) {
-			return $this->controller->Session->read("$this->_branchKey.$branch");
-		}
-		return false;
-	}
-/**
- * Parses the steps array by stripping off nested arrays not included in the branches
- * and returns a simple array with the correct steps. 
- *
- * @param array $steps Array to be parsed for nested arrays and returned as simple array.
- * @return array
- * @access protected
- */	
-	protected function _parseSteps($steps) {
-		$parsed = array();
-
-		foreach ($steps as $key => $name) {
-			if (is_array($name)) { 
-				foreach ($name as $branchName => $step) {
-					$branchType = $this->_branchType($branchName);
-
-					if ($branchType) {
-						if ($branchType !== 'skip') {
-							$branch = $branchName;
-						}
-					} elseif (empty($branch) && $this->defaultBranch) {
-						$branch = $branchName;
-					}
-				}
-				
-				if (!empty($branch)) {
-					if (is_array($name[$branch])) {
-						$parsed = array_merge($parsed, $this->_parseSteps($name[$branch]));
-					} else {
-						$parsed[] = $name[$branch];
-					}
-				}
-				
-				unset($branch);
-			} else {
-				$parsed[] = $name;
-			}
-		}
-		return $parsed;
-	}
-/**
- * Moves internal array pointer of $this->steps to $step and sets $this->_currentStep.
- *
- * @param $step Step to point to.
- * @access protected
- */		
-	protected function _setCurrentStep($step) {
-		$this->_currentStep = reset($this->steps);
-		
-		while(current($this->steps) != $step) {
-			$this->_currentStep = next($this->steps);
-		}
-	}
-/**
- * Validates controller data with the correct model if the model is included in
- * the controller's uses array. This only occurs if $autoValidate = true and there
- * is no processCallback in the controller for the current step.
- *
- * @return boolean
- * @access protected
- */	
-	protected function _validateData() {
-		$controller =& $this->controller;
-		
-		foreach ($controller->data as $model => $data) {
-			if (in_array($model, $controller->uses)) {
-				$controller->{$model}->set($data);
-				
-				if (!$controller->{$model}->validates()) {
-					return false;
-				}
-			}
-		}
-		return true;
-	}
-/**
- * Validates the $step four ways:
- *   1. Explicitly only validate step that exists in $this->steps array.
- *   2. If $roaming option is true any steps within $this->steps is valid
- *   3. If $lockdown option is true only the next/current step is valid.
- *   4. If $roaming and $lockdown is false validate the step either before or exactly the expected step.
- *
- * @param $step Step to validate.
- * @return mixed
- * @access protected
- */		
-	protected function _validStep($step) {
-		if (in_array($step, $this->steps)) {
-			if($this->roaming) {
-				return true;
-			} elseif ($this->lockdown) {
-				return (array_search($step, $this->steps) == array_search($this->_getExpectedStep(), $this->steps));
-			}
-			return (array_search($step, $this->steps) <= array_search($this->_getExpectedStep(), $this->steps));
-		}
-		return false;
-	}
-        
 
 }
-?>

From 76fa47ef1678220b0e45d3956abde669d3437ec2 Mon Sep 17 00:00:00 2001
From: Lucas Freitas 
Date: Mon, 24 Feb 2014 01:21:38 -0300
Subject: [PATCH 37/48] Code reformatting and PHPDoc fixing

---
 View/Helper/WizardHelper.php | 268 +++++++++++++++++++----------------
 1 file changed, 146 insertions(+), 122 deletions(-)

diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php
index 7072d21..de1af96 100644
--- a/View/Helper/WizardHelper.php
+++ b/View/Helper/WizardHelper.php
@@ -1,4 +1,5 @@
 Session->read('Wizard.config');
-		} else {
-			$wizardData = $this->Session->read('Wizard.config.'.$key);
-			if (!empty($wizardData)) {
-				return $wizardData;
-			} else {
-				return null;
-			}
-		}
-	}
-/**
- * undocumented function
- *
- * @param string $title 
- * @param string $step 
- * @param string $htmlAttributes 
- * @param string $confirmMessage 
- * @param string $escapeTitle 
- * @return string link to a specific step
+ * @author             jaredhoyt
+ * @package            Cake.View.Helper
+ * @lastmodified       Date: March 11, 2009
+ * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
  */
-	public function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
-		if ($step == null) {
-			$step = $title;
-		}
-		$wizardAction = $this->config('wizardAction');
-		
-		return $this->Html->link($title, $wizardAction.$step, $htmlAttributes, $confirmMessage, $escapeTitle);
-	}
-/**
- * Retrieve the step number of the specified step name, or the active step
- *
- * @param string $step optional name of step
- * @param string $shiftIndex optional offset of returned array index. Default 1
- * @return string step number. Returns false if not found
- */
-	public function stepNumber($step = null, $shiftIndex = 1) {
-		if ($step == null) {
-			$step = $this->config('activeStep');
-		}
-		
-		$steps = $this->config('steps');
-		
-		if (in_array($step, $steps)) {
-			return array_search($step, $steps) + $shiftIndex;
-		} else {
-			return false;
-		}
-	}
-
-	public function stepTotal()
-	{
-		$steps = $this->config('steps');
-		return count($steps);
-	}
+class WizardHelper extends AppHelper
+{
+    public $helpers = array('Session', 'Html', 'Form');
+    public $output = null;
 
-/**
- * Returns a set of html elements containing links for each step in the wizard. 
- *
- * @param string $titles 
- * @param string $attributes pass a value for 'wrap' to change the default tag used
- * @param string $htmlAttributes 
- * @param string $confirmMessage 
- * @param string $escapeTitle 
- * @return string
- */
-	public function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
-		$wizardConfig = $this->config();
-		extract($wizardConfig);	
-                $wizardAction = $this->config('wizardAction');
-
-		$attributes = array_merge(array('wrap' => 'div'), $attributes);
-		extract($attributes);
-		
-		$incomplete = null;
-
-		foreach ($steps as $title => $step) {
-			$title = empty($titles[$step]) ? $step : $titles[$step];
-			
-			if (!$incomplete) {
-				if ($step == $expectedStep) {
-					$incomplete = true;
-					$class = 'expected';
-				} else {
-					$class = 'complete';
-				}
-				if ($step == $activeStep) {
-					$class .= ' active';
-				}
-				$this->output .= "<$wrap class='$class'>" . $this->Html->link($title, array('action' => $wizardAction, $step), $htmlAttributes, $confirmMessage, $escapeTitle) . "";
-			} else {
-				$this->output .= "<$wrap class='incomplete'>" . $title . "";
-			}
-		}
-		
-		return $this->output;
-	}
-/**
- * Wrapper for Form->create()
- *
- * @param string $model 
- * @param array $options 
- * @return string
- */
-	public function create($model = null, $options = array()) {
-		if (!isset($options['url']) || !in_array($this->params['pass'][0], $options['url']))
-			$options['url'][] = $this->params['pass'][0];
-		return $this->Form->create($model, $options);
-	}
+    /**
+     * undocumented function
+     *
+     * @param string $key optional key to retrieve the existing value
+     *
+     * @return mixed data at config key (if key is passed)
+     */
+    public function config($key = null)
+    {
+        if ($key == null) {
+            return $this->Session->read('Wizard.config');
+        } else {
+            $wizardData = $this->Session->read('Wizard.config.' . $key);
+            if (!empty($wizardData)) {
+                return $wizardData;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * undocumented function
+     *
+     * @param string  $title
+     * @param string  $step
+     * @param array   $htmlAttributes
+     * @param boolean $confirmMessage
+     * @param boolean $escapeTitle
+     *
+     * @return string link to a specific step
+     */
+    public function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true)
+    {
+        if ($step == null) {
+            $step = $title;
+        }
+        $wizardAction = $this->config('wizardAction');
+
+        return $this->Html->link($title, $wizardAction . $step, $htmlAttributes, $confirmMessage, $escapeTitle);
+    }
+
+    /**
+     * Retrieve the step number of the specified step name, or the active step
+     *
+     * @param string $step       optional name of step
+     * @param int    $shiftIndex optional offset of returned array index. Default 1
+     *
+     * @return string step number. Returns false if not found
+     */
+    public function stepNumber($step = null, $shiftIndex = 1)
+    {
+        if ($step == null) {
+            $step = $this->config('activeStep');
+        }
+
+        $steps = $this->config('steps');
+
+        if (in_array($step, $steps)) {
+            return array_search($step, $steps) + $shiftIndex;
+        } else {
+            return false;
+        }
+    }
+
+    public function stepTotal()
+    {
+        $steps = $this->config('steps');
+
+        return count($steps);
+    }
+
+    /**
+     * Returns a set of html elements containing links for each step in the wizard.
+     *
+     * @param array   $titles
+     * @param array   $attributes pass a value for 'wrap' to change the default tag used
+     * @param array   $htmlAttributes
+     * @param boolean $confirmMessage
+     * @param boolean $escapeTitle
+     *
+     * @return string
+     */
+    public function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true)
+    {
+        $wizardConfig = $this->config();
+        extract($wizardConfig);
+        $wizardAction = $this->config('wizardAction');
+
+        $attributes = array_merge(array('wrap' => 'div'), $attributes);
+        /**
+         * @var   array  $steps
+         * @var   string $expectedStep
+         * @var   string $activeStep
+         * @var   string $wrap
+         */
+        extract($attributes);
+
+        $incomplete = null;
+
+        foreach ($steps as $title => $step) {
+            $title = empty($titles[$step]) ? $step : $titles[$step];
+
+            if (!$incomplete) {
+                if ($step == $expectedStep) {
+                    $incomplete = true;
+                    $class      = 'expected';
+                } else {
+                    $class = 'complete';
+                }
+                if ($step == $activeStep) {
+                    $class .= ' active';
+                }
+                $this->output .= "<$wrap class='$class'>" . $this->Html->link($title, array('action' => $wizardAction, $step), $htmlAttributes, $confirmMessage, $escapeTitle) . "";
+            } else {
+                $this->output .= "<$wrap class='incomplete'>" . $title . "";
+            }
+        }
+
+        return $this->output;
+    }
+
+    /**
+     * Wrapper for Form->create()
+     *
+     * @param string $model
+     * @param array  $options
+     *
+     * @return string
+     */
+    public function create($model = null, $options = array())
+    {
+        if (!isset($options['url']) || !in_array($this->params['pass'][0], $options['url']))
+            $options['url'][] = $this->params['pass'][0];
+
+        return $this->Form->create($model, $options);
+    }
 }
-?>
\ No newline at end of file

From 05b644c361037beb1b3470d89da97b52c50616a0 Mon Sep 17 00:00:00 2001
From: webcrab 
Date: Mon, 17 Mar 2014 19:09:49 +0100
Subject: [PATCH 38/48] add return before every redirect()

allows proper controller testing.
http://book.cakephp.org/2.0/en/development/testing.html#testing-controllers
---
 Controller/Component/WizardComponent.php | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index 5256a98..55464ca 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -232,7 +232,7 @@ public function process($step)
                 $this->controller->_beforeCancel($this->_getExpectedStep());
             }
             $this->reset();
-            $this->controller->redirect($this->cancelUrl);
+            return $this->controller->redirect($this->cancelUrl);
         }
         if (isset($this->controller->request->data['Draft'])) {
             if (method_exists($this->controller, '_saveDraft')) {
@@ -241,7 +241,7 @@ public function process($step)
             }
 
             $this->reset();
-            $this->controller->redirect($this->draftUrl);
+            return $this->controller->redirect($this->draftUrl);
         }
 
         if (empty($step)) {
@@ -250,7 +250,7 @@ public function process($step)
                     $this->controller->_afterComplete();
                 }
                 $this->reset();
-                $this->controller->redirect($this->completeUrl);
+                return $this->controller->redirect($this->completeUrl);
             }
 
             $this->autoReset = false;
@@ -279,17 +279,17 @@ public function process($step)
 
                         if (next($this->steps)) {
                             if ($this->autoAdvance) {
-                                $this->redirect();
+                                return $this->redirect();
                             }
-                            $this->redirect(current($this->steps));
+                            return $this->redirect(current($this->steps));
                         } else {
                             $this->controller->Session->write('Wizard.complete', $this->read());
                             $this->reset();
-                            $this->controller->redirect(array('action' => $this->action));
+                            return $this->controller->redirect(array('action' => $this->action));
                         }
                     }
                 } elseif (isset($this->controller->request->data['Previous']) && prev($this->steps)) {
-                    $this->redirect(current($this->steps));
+                    return $this->redirect(current($this->steps));
                 } elseif ($this->controller->Session->check("$this->_sessionKey._draft.current")) {
                     $this->controller->data = $this->read('_draft.current.data');
                     $this->controller->Session->delete("$this->_sessionKey._draft.current");
@@ -310,7 +310,7 @@ public function process($step)
 
                 return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true;
             } else {
-                $this->redirect();
+                return $this->redirect();
             }
         }
 
@@ -318,7 +318,7 @@ public function process($step)
             $this->reset();
         }
 
-        $this->redirect();
+        return $this->redirect();
     }
 
     /**
@@ -377,9 +377,9 @@ public function loadDraft($draft = array())
     {
         if (!empty($draft['_draft']['current']['step'])) {
             $this->restore($draft);
-            $this->redirect($draft['_draft']['current']['step']);
+            return $this->redirect($draft['_draft']['current']['step']);
         }
-        $this->redirect();
+        return $this->redirect();
     }
 
     /**
@@ -417,7 +417,7 @@ public function redirect($step = null, $status = null, $exit = true)
             $step = $this->_getExpectedStep();
         }
         $url = array('controller' => $this->controller->request->controller, 'action' => $this->action, $step);
-        $this->controller->redirect($url, $status, $exit);
+        return $this->controller->redirect($url, $status, $exit);
     }
 
     /**

From d3205c55c95bf73fecefc80623f365c9a9658fae Mon Sep 17 00:00:00 2001
From: destinydriven 
Date: Wed, 13 Aug 2014 09:57:31 -0400
Subject: [PATCH 39/48] Update README.md

---
 README.md | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/README.md b/README.md
index 56a3050..8e4cd36 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,3 @@ The Wizard plugin for CakePHP automates several aspects of multi-page forms incl
 ## Documentation
 
 Detailed documentation, including usage examples, can be found in the [GitHub wiki](http://github.com/jaredhoyt/cakephp-wizard/wiki).
-
-## Reporting issues
-
-If you have any issues with this plugin, please open a ticket on [Lighthouse](http://jaredhoyt.lighthouseapp.com/projects/60073-cakephp-wizard).

From 5a9f441292205623b36d897c356c8b77c36cf4d5 Mon Sep 17 00:00:00 2001
From: Dean Sofer 
Date: Fri, 7 Aug 2015 13:41:12 -0700
Subject: [PATCH 40/48] Update composer.json

---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 825f8c7..eb37125 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
 {
-    "name": "lucasff/cakephp-wizard",
+    "name": "proloser/cakephp-wizard",
     "description": "CakePHP Wizard Plugin",
     "type": "cakephp-plugin",
     "keywords": ["cakephp", "wizard"],

From a7fe70efbcdb1b7f1c207540727da3560f72c86c Mon Sep 17 00:00:00 2001
From: Val Bancer 
Date: Fri, 25 Nov 2016 00:23:14 +0200
Subject: [PATCH 41/48] fixed code style (#17)

* updated gitignore

* updated gitignore and composer

* improved WizardHelper code style

* fixed WizardComponent code style
---
 .gitignore                               |  7 +-
 Controller/Component/WizardComponent.php | 82 ++++++++++--------------
 View/Helper/WizardHelper.php             | 49 +++++++-------
 composer.json                            |  5 ++
 4 files changed, 69 insertions(+), 74 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4df8f53..cd97ac8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,7 @@
 *~
-.DS_Store
\ No newline at end of file
+.DS_Store
+/.buildpath
+/.project
+/.settings/
+/vendor/
+/composer.lock
diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index 54aa152..683d989 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -31,7 +31,7 @@ class WizardComponent extends Component {
  * the expected step after editing a previous step w/o them having to navigate through
  * each step in between.)
  *
- * @var boolean
+ * @var bool
  * @access public
  */
 	public $autoAdvance = true;
@@ -42,7 +42,7 @@ class WizardComponent extends Component {
  * Set this to false if you want the Wizard to return to the "expected step"
  * after invalid navigation.
  *
- * @var boolean
+ * @var bool
  * @access public
  */
 	public $autoReset = false;
@@ -51,7 +51,7 @@ class WizardComponent extends Component {
  * If no processCallback() exists for the current step, the component will automatically
  * validate the model data against the models included in the controller's uses array.
  *
- * @var boolean
+ * @var bool
  * @access public
  */
 	public $autoValidate = false;
@@ -109,7 +109,7 @@ class WizardComponent extends Component {
  * If true, the first "non-skipped" branch in a group will be used if a branch has
  * not been included specifically.
  *
- * @var boolean
+ * @var bool
  * @access public
  */
 	public $defaultBranch = true;
@@ -118,7 +118,7 @@ class WizardComponent extends Component {
  * If true, the user will not be allowed to edit previously completed steps. They will be
  * "locked down" to the current step. The opposite of $roaming.
  *
- * @var boolean
+ * @var bool
  * @access public
  */
 	public $lockdown = false;
@@ -126,7 +126,7 @@ class WizardComponent extends Component {
 /**
  * If true, the user will be allowed navigate to any steps. The opposite of $lockdown.
  *
- * @var boolean
+ * @var bool
  * @access public
  */
 	public $roaming = false;
@@ -135,17 +135,17 @@ class WizardComponent extends Component {
  * If true, the component will render views found in views/{wizardAction}/{step}.ctp rather
  *  than views/{step}.ctp.
  *
- * @var boolean
+ * @var bool
  * @access public
  */
 	public $nestedViews = false;
 
 /**
-* Other components used.
-*
-* @var array
-* @access public
-*/
+ * Other components used.
+ *
+ * @var array
+ * @access public
+ */
 	public $components = array('Session');
 
 /**
@@ -205,7 +205,6 @@ public function __construct(ComponentCollection $collection, $settings = array()
  */
 	public function initialize(Controller $controller) {
 		$this->controller = $controller;
-
 		$this->_sessionKey = $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name;
 		$this->_configKey = 'Wizard.config';
 		$this->_branchKey = 'Wizard.branches.' . $controller->name;
@@ -218,13 +217,12 @@ public function initialize(Controller $controller) {
  * @param \Controller|object $controller A reference to the instantiating controller object
  *
  * @access public
+ * @return void
  */
 	public function startup(Controller $controller) {
 		$this->steps = $this->_parseSteps($this->steps);
-
 		$this->config('action', $this->action);
 		$this->config('steps', $this->steps);
-
 		if (!in_array('Wizard.Wizard', $this->controller->helpers) && !array_key_exists('Wizard.Wizard', $this->controller->helpers)) {
 			$this->controller->helpers[] = 'Wizard.Wizard';
 		}
@@ -241,12 +239,10 @@ public function startup(Controller $controller) {
  */
 	protected function _parseSteps($steps) {
 		$parsed = array();
-
 		foreach ($steps as $key => $name) {
 			if (is_array($name)) {
 				foreach ($name as $branchName => $step) {
 					$branchType = $this->_branchType($branchName);
-
 					if ($branchType) {
 						if ($branchType !== 'skip') {
 							$branch = $branchName;
@@ -255,7 +251,6 @@ protected function _parseSteps($steps) {
 						$branch = $branchName;
 					}
 				}
-
 				if (!empty($branch)) {
 					if (is_array($name[$branch])) {
 						$parsed = array_merge($parsed, $this->_parseSteps($name[$branch]));
@@ -263,7 +258,6 @@ protected function _parseSteps($steps) {
 						$parsed[] = $name[$branch];
 					}
 				}
-
 				unset($branch);
 			} else {
 				$parsed[] = $name;
@@ -275,7 +269,7 @@ protected function _parseSteps($steps) {
 /**
  * Saves configuration details for use in WizardHelper.
  *
- * @param $branch
+ * @param string $branch branch key.
  *
  * @return mixed
  * @access protected
@@ -335,11 +329,9 @@ public function process($step) {
 				);
 				$this->controller->_saveDraft(array_merge_recursive((array)$this->read(), $draft));
 			}
-
 			$this->reset();
 			$this->controller->redirect($this->draftUrl);
 		}
-
 		if (empty($step)) {
 			if ($this->controller->Session->check('Wizard.complete')) {
 				if (method_exists($this->controller, '_afterComplete')) {
@@ -348,7 +340,6 @@ public function process($step) {
 				$this->reset();
 				$this->controller->redirect($this->completeUrl);
 			}
-
 			$this->autoReset = false;
 		} elseif ($step == 'reset') {
 			if (!$this->lockdown) {
@@ -357,7 +348,6 @@ public function process($step) {
 		} else {
 			if ($this->_validStep($step)) {
 				$this->_setCurrentStep($step);
-
 				if (!empty($this->controller->data) && !isset($this->controller->request->data['Previous'])) {
 					$processCallback = '_' . Inflector::variable('process_' . $this->_currentStep);
 					if (method_exists($this->controller, $processCallback)) {
@@ -367,10 +357,8 @@ public function process($step) {
 					} else {
 						throw new NotImplementedException(sprintf(__('Process Callback not found. Please create Controller::%s', $processCallback)));
 					}
-
 					if ($proceed) {
 						$this->save();
-
 						if (isset($this->controller->request->data['SaveAndBack']) && prev($this->steps)) {
 							$this->redirect(current($this->steps));
 						}
@@ -393,28 +381,22 @@ public function process($step) {
 				} elseif ($this->controller->Session->check("$this->_sessionKey.$this->_currentStep")) {
 					$this->controller->data = $this->read($this->_currentStep);
 				}
-
 				$prepareCallback = '_' . Inflector::variable('prepare_' . $this->_currentStep);
 				if (method_exists($this->controller, $prepareCallback)) {
 					$this->controller->$prepareCallback();
 				}
-
 				$this->config('activeStep', $this->_currentStep);
-
 				if ($this->nestedViews) {
 					$this->controller->viewPath .= '/' . $this->action;
 				}
-
 				return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true;
 			} else {
 				$this->redirect();
 			}
 		}
-
 		if ($step != 'reset' && $this->autoReset) {
 			$this->reset();
 		}
-
 		$this->redirect();
 	}
 
@@ -438,6 +420,7 @@ protected function _getExpectedStep() {
  * Resets the wizard by deleting the wizard session.
  *
  * @access public
+ * @return void
  */
 	public function reset() {
 		$this->controller->Session->delete($this->_branchKey);
@@ -447,7 +430,7 @@ public function reset() {
 /**
  * Get the data from the Session that has been stored by the WizardComponent.
  *
- * @param null $key
+ * @param string $key step key.
  *
  * @internal param mixed $name The name of the session variable (or a path as sent to Set.extract)
  *
@@ -470,7 +453,7 @@ public function read($key = null) {
  *   3. If $lockdown option is true only the next/current step is valid.
  *   4. If $roaming and $lockdown is false validate the step either before or exactly the expected step.
  *
- * @param $step Step to validate.
+ * @param string $step Step to validate.
  *
  * @return mixed
  * @access protected
@@ -490,13 +473,13 @@ protected function _validStep($step) {
 /**
  * Moves internal array pointer of $this->steps to $step and sets $this->_currentStep.
  *
- * @param string $step, Step to point to.
+ * @param string $step Step to point to.
  *
  * @access protected
+ * @return void
  */
 	protected function _setCurrentStep($step) {
 		$this->_currentStep = reset($this->steps);
-
 		while (current($this->steps) != $step) {
 			$this->_currentStep = next($this->steps);
 		}
@@ -507,16 +490,14 @@ protected function _setCurrentStep($step) {
  * the controller's uses array. This only occurs if $autoValidate = true and there
  * is no processCallback in the controller for the current step.
  *
- * @return boolean
+ * @return bool
  * @access protected
  */
 	protected function _validateData() {
 		$controller =& $this->controller;
-
 		foreach ($controller->request->data as $model => $data) {
 			if (in_array($model, $controller->uses)) {
 				$controller->{$model}->set($data);
-
 				if (!$controller->{$model}->validates()) {
 					return false;
 				}
@@ -531,7 +512,10 @@ protected function _validateData() {
  * Please note: This is normally called automatically by the component after
  * a successful _processCallback, but can be called directly for advanced navigation purposes.
  *
+ * @param string $step step key.
+ * @param array $data  step details.
  * @access public
+ * @return void
  */
 	public function save($step = null, $data = null) {
 		if (is_null($step)) {
@@ -547,11 +531,12 @@ public function save($step = null, $data = null) {
  * Handles Wizard redirection. A null url will redirect to the "expected" step.
  *
  * @param string  $step   Stepname to be redirected to.
- * @param integer $status Optional HTTP status code (eg: 404)
- * @param boolean $exit   If true, exit() will be called after the redirect
+ * @param int $status Optional HTTP status code (eg: 404)
+ * @param bool $exit   If true, exit() will be called after the redirect
  *
  * @see    Controller::redirect()
  * @access public
+ * @return void
  */
 	public function redirect($step = null, $status = null, $exit = true) {
 		if ($step == null) {
@@ -569,24 +554,21 @@ public function redirect($step = null, $status = null, $exit = true) {
  * Selects a branch to be used in the steps array. The first branch in a group is included by default.
  *
  * @param string  $name Branch name to be included in steps.
- * @param boolean $skip Branch will be skipped instead of included if true.
+ * @param bool $skip Branch will be skipped instead of included if true.
  *
  * @access public
+ * @return void
  */
 	public function branch($name, $skip = false) {
 		$branches = array();
-
 		if ($this->controller->Session->check($this->_branchKey)) {
 			$branches = $this->controller->Session->read($this->_branchKey);
 		}
-
 		if (isset($branches[$name])) {
 			unset($branches[$name]);
 		}
-
 		$value = $skip ? 'skip' : 'branch';
 		$branches[$name] = $value;
-
 		$this->controller->Session->write($this->_branchKey, $branches);
 	}
 
@@ -597,6 +579,7 @@ public function branch($name, $skip = false) {
  *
  * @see    WizardComponent::process()
  * @access public
+ * @return void
  */
 	public function loadDraft($draft = array()) {
 		if (!empty($draft['_draft']['current']['step'])) {
@@ -613,6 +596,7 @@ public function loadDraft($draft = array()) {
  * @param array $data Data to be written to controller's wizard session.
  *
  * @access public
+ * @return void
  */
 	public function restore($data = array()) {
 		$this->controller->Session->write($this->_sessionKey, $data);
@@ -622,6 +606,7 @@ public function restore($data = array()) {
  * Resets the wizard by deleting the wizard session.
  *
  * @access public
+ * @return void
  */
 	public function resetWizard() {
 		$this->reset();
@@ -630,11 +615,12 @@ public function resetWizard() {
 /**
  * Resets the data from the Session that has been stored by the WizardComponent.
  *
- * @param null $key
+ * @param string $key step key.
  *
  * @internal param mixed $name The name of the session variable (or a path as sent to Set.extract)
  *
  * @access   public
+ * @return void
  */
 	public function delete($key = null) {
 		if ($key == null) {
@@ -651,10 +637,10 @@ public function delete($key = null) {
  * @param string $branch Name of branch to be removed from steps array.
  *
  * @access public
+ * @return void
  */
 	public function unbranch($branch) {
 		$this->controller->Session->delete("$this->_branchKey.$branch");
 	}
-        
 
 }
diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php
index 2afeeab..371edf5 100644
--- a/View/Helper/WizardHelper.php
+++ b/View/Helper/WizardHelper.php
@@ -13,11 +13,13 @@
  * @property HtmlHelper $Html
  */
 class WizardHelper extends AppHelper {
+
 	public $helpers = array(
 		'Session',
 		'Html',
 		'Form'
 	);
+
 	public $output = null;
 
 /**
@@ -42,20 +44,19 @@ public function config($key = null) {
 /**
  * undocumented function
  *
- * @param string       $title
- * @param string       $step
- * @param array|string $htmlAttributes
- * @param bool|string  $confirmMessage
- * @param bool|string  $escapeTitle
+ * @param string       $title          The content to be wrapped by `` tags.
+ * @param string       $step           Form step.
+ * @param array|string $htmlAttributes Array of options and HTML attributes.
+ * @param bool|string  $confirmMessage JavaScript confirmation message. This
+ *   argument is deprecated as of 2.6. Use `confirm` key in $options instead.
  * @return string link to a specific step
  */
-	public function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
+	public function link($title, $step = null, $htmlAttributes = array(), $confirmMessage = false) {
 		if ($step == null) {
 			$step = $title;
 		}
 		$wizardAction = $this->config('wizardAction');
-
-		return $this->Html->link($title, $wizardAction . $step, $htmlAttributes, $confirmMessage, $escapeTitle);
+		return $this->Html->link($title, $wizardAction . $step, $htmlAttributes, $confirmMessage);
 	}
 
 /**
@@ -69,9 +70,7 @@ public function stepNumber($step = null, $shiftIndex = 1) {
 		if ($step == null) {
 			$step = $this->config('activeStep');
 		}
-
 		$steps = $this->config('steps');
-
 		if (in_array($step, $steps)) {
 			return array_search($step, $steps) + $shiftIndex;
 		} else {
@@ -79,6 +78,11 @@ public function stepNumber($step = null, $shiftIndex = 1) {
 		}
 	}
 
+/**
+ * Counts the total number of steps.
+ *
+ * @return int
+ */
 	public function stepTotal() {
 		$steps = $this->config('steps');
 		return count($steps);
@@ -87,27 +91,23 @@ public function stepTotal() {
 /**
  * Returns a set of html elements containing links for each step in the wizard.
  *
- * @param array|string $titles
- * @param array|string $attributes pass a value for 'wrap' to change the default tag used
- * @param array|string $htmlAttributes
- * @param bool|string  $confirmMessage
- * @param bool|string  $escapeTitle
- *
+ * @param array|string $titles         Array of form steps where the keys are
+ *   the steps and the values are the titles to be used for links.
+ * @param array|string $attributes     pass a value for 'wrap' to change the default tag used
+ * @param array|string $htmlAttributes Array of options and HTML attributes.
+ * @param bool|string  $confirmMessage JavaScript confirmation message. This
+ *   argument is deprecated as of 2.6. Use `confirm` key in $options instead.
  * @return string
  */
-	public function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
+	public function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false) {
 		$wizardConfig = $this->config();
 		extract($wizardConfig);
 		$wizardAction = $this->config('wizardAction');
-
 		$attributes = array_merge(array('wrap' => 'div'), $attributes);
 		extract($attributes);
-
 		$incomplete = null;
-
 		foreach ($steps as $title => $step) {
 			$title = empty($titles[$step]) ? $step : $titles[$step];
-
 			if (!$incomplete) {
 				if ($step == $expectedStep) {
 					$incomplete = true;
@@ -121,20 +121,19 @@ public function progressMenu($titles = array(), $attributes = array(), $htmlAttr
 				$this->output .= "<$wrap class='$class'>" . $this->Html->link($title, array(
 						'action' => $wizardAction,
 						$step
-					), $htmlAttributes, $confirmMessage, $escapeTitle) . "";
+					), $htmlAttributes, $confirmMessage) . "";
 			} else {
 				$this->output .= "<$wrap class='incomplete'>" . $title . "";
 			}
 		}
-
 		return $this->output;
 	}
 
 /**
  * Wrapper for Form->create()
  *
- * @param string $model
- * @param array  $options
+ * @param string $model   The model name for which the form is being defined.
+ * @param array  $options An array of html attributes and options.
  *
  * @return string
  */
diff --git a/composer.json b/composer.json
index eb37125..0f215f2 100644
--- a/composer.json
+++ b/composer.json
@@ -20,6 +20,11 @@
         "php": ">=5.3.0",
         "composer/installers": "*"
     },
+    "require-dev": {
+        "phpunit/phpunit":"3.7.*",
+        "phpmd/phpmd" : "@stable",
+        "cakephp/cakephp-codesniffer": "1.*"
+    },
     "extra": {
         "installer-name": "Wizard"
     }

From 0ba5cbae9580595a526d6a027419c3782503ab77 Mon Sep 17 00:00:00 2001
From: Val Bancer 
Date: Wed, 30 Nov 2016 21:29:30 +0200
Subject: [PATCH 42/48] unit tests added, code style fixed (#18)

* updated gitignore

* updated gitignore and composer

* improved WizardHelper code style

* fixed WizardComponent code style

* added .travis.yml and a test file

* improved travis config, removed wrong import

* added phpcs to travis config

* improved travis config

* fixed travis phpcs path

* improved documentation

* fixed code style

* improved unit test

* fixed testInitialize()

* fixed unit tests, added code coverage

* adjusted coverage config

* temporary commented out unused test code

* added unit test, adjusted coverage config

* adjusted unit tests

* adjusted coverage config

* adjusted coverage config

* adjusted coverage config

* adjusted coverage config

* adjusted coverage config

* adjusted coverage config

* adjusted coverage configuration

* adjusted coverage configuration

* improved unit test

* improved documentation and code style, more unit testing

* fixed typo

* fixed undefined variable

* fixed unit test

* branch() method simplified and unit tested

* improved code style

* more unit tests

* more unit tests

* fixed unit test

* fixed unit test

* fixed unit test

* adjusted codecov.io call

* adjusted codecov call

* improved unit test

* improved unit test

* more unit tests

* fixed unit test

* more unit tests

* improved unit tests

* improved unit tests

* fixed unit tests

* improved unit tests

* improved unit tests

* improved unit tests

* simplified unit tests

* improved unit tests

* temporary comment out yet unused code

* more unit tests

* improved code style and unit tests

* improved unit test

* fixed unit test

* improved code style, unit tests

* fixed unit test

* fixed $this->controller->data, improved unit tests

* fixed unit test

* improved unit test

* unit tests

* unit tests

* more debug

* unit tests

* unit tests

* more debug

* more debug

* more debug

* more debug

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* code style, unit tests

* fixed unit tests

* more unit tests

* added missing import

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* more unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* unit tests

* reenable stylecheck

* fixed code style
---
 .travis.yml                                   |  90 +++
 Controller/Component/WizardComponent.php      | 129 +++--
 README3.md                                    |  18 +-
 Test/Case/AllWizardTest.php                   |   9 +
 .../Component/WizardComponentTest.php         | 526 ++++++++++++++++++
 phpunit-clover.xml                            |  20 +
 6 files changed, 725 insertions(+), 67 deletions(-)
 create mode 100644 .travis.yml
 create mode 100644 Test/Case/AllWizardTest.php
 create mode 100644 Test/Case/Controller/Component/WizardComponentTest.php
 create mode 100644 phpunit-clover.xml

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c9b0285
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,90 @@
+# Thanks cakephp/debug_kit for this file.
+language: php
+
+php:
+#  - 5.5
+  - 5.6
+#  - 7.0
+#  - 7.1
+
+env:
+#  - CAKE_VERSION=2.8.0 DB=mysql
+  - CAKE_VERSION=2.9.2 DB=mysql
+
+install:
+  - git clone git://github.com/cakephp/cakephp ../cakephp && cd ../cakephp && git checkout $CAKE_VERSION
+  - cp -R ../cakephp-wizard plugins/Wizard
+  - chmod -R 777 ../cakephp/app/tmp
+  - sh -c "composer global require 'phpunit/phpunit=3.7.33'"
+  - sh -c "ln -s ~/.composer/vendor/phpunit/phpunit/PHPUnit ../cakephp/vendors/PHPUnit"
+  - sh -c "composer global require 'cakephp/cakephp-codesniffer:1.*'"
+  - sh -c "~/.composer/vendor/bin/phpcs --config-set installed_paths ~/.composer/vendor/cakephp/cakephp-codesniffer"
+
+before_script:
+  - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi"
+  - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi"
+  - set +H
+  - echo " array(
+        'datasource' => 'Database/Mysql',
+        'host' => '0.0.0.0',
+        'login' => 'travis'
+      ),
+      'pgsql' => array(
+        'datasource' => 'Database/Postgres',
+        'host' => '127.0.0.1',
+        'login' => 'postgres',
+        'database' => 'cakephp_test',
+        'schema' => array(
+          'default' => 'public',
+          'test' => 'public'
+        )
+      )
+    );
+    public \$default = array(
+      'persistent' => false,
+      'host' => '',
+      'login' => '',
+      'password' => '',
+      'database' => 'cakephp_test',
+      'prefix' => ''
+    );
+    public \$test = array(
+      'persistent' => false,
+      'host' => '',
+      'login' => '',
+      'password' => '',
+      'database' => 'cakephp_test',
+      'prefix' => ''
+    );
+    public function __construct() {
+      \$db = 'mysql';
+      if (!empty(\$_SERVER['DB'])) {
+        \$db = \$_SERVER['DB'];
+      }
+      foreach (array('default', 'test') as \$source) {
+        \$config = array_merge(\$this->{\$source}, \$this->identities[\$db]);
+        if (is_array(\$config['database'])) {
+          \$config['database'] = \$config['database'][\$source];
+        }
+        if (!empty(\$config['schema']) && is_array(\$config['schema'])) {
+          \$config['schema'] = \$config['schema'][\$source];
+        }
+        \$this->{\$source} = \$config;
+      }
+    }
+    }" > ../cakephp/app/Config/database.php
+
+script:
+# Temporary disable code style until we have enough unit tests to fix code style errors.
+  - ~/.composer/vendor/bin/phpcs --standard=CakePHP ./plugins/Wizard --ignore=*/vendor/* -p
+  - ./lib/Cake/Console/cake test Wizard AllWizard --configuration=./plugins/Wizard/phpunit-clover.xml --stderr
+
+after_success:
+#  - bash <(curl -s https://codecov.io/bash)
+  - cd ./plugins/Wizard && bash <(curl -s https://codecov.io/bash)
+
+notifications:
+  email: false
diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php
index 683d989..87980a6 100644
--- a/Controller/Component/WizardComponent.php
+++ b/Controller/Component/WizardComponent.php
@@ -2,7 +2,8 @@
 /**
  * Wizard component by jaredhoyt.
  *
- * Handles multi-step form navigation, data persistence, validation callbacks, and plot-branching navigation.
+ * Handles multi-step form navigation, data persistence, validation callbacks,
+ * and plot-branching navigation.
  *
  * PHP versions 4 and 5
  *
@@ -10,7 +11,7 @@
  *
  * Licensed under The MIT License
  *
- * @property $Session SessionComponent
+ * @property SessionComponent $Session
  * @writtenby          jaredhoyt
  * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
  */
@@ -58,14 +59,29 @@ class WizardComponent extends Component {
 
 /**
  * List of steps, in order, that are to be included in the wizard.
- *        basic example: $steps = array('contact', 'payment', 'confirm');
- *
- * The $steps array can also contain nested steps arrays of the same format but must be wrapped by a branch group.
- *        plot-branched example: $steps = array('job_application', array('degree' => array('college', 'degree_type'), 'nodegree' => 'experience'), 'confirm');
- *
- * The 'branchnames' (ie 'degree', 'nodegree') are arbitrary but used as selectors for the branch() and unbranch() methods. Branches
- * can point to either another steps array or a single step. The first branch in a group that hasn't been skipped (see branch())
- * is included by default (if $defaultBranch = true).
+ * Basic example:
+ * 
+ * $steps = array('contact', 'payment', 'confirm');
+ * 
+ *
+ * The $steps array can also contain nested steps arrays of the same format but
+ * must be wrapped by a branch group.
+ * Plot-branched example:
+ * 
+ * $steps = array(
+ *     'job_application',
+ *         array(
+ *             'degree' => array('college', 'degree_type'),
+ *             'nodegree' => 'experience'
+ *         ),
+ *         'confirm',
+ *     );
+ * 
+ *
+ * The 'branchnames' (ie 'degree', 'nodegree') are arbitrary but used as selectors
+ * for the branch() and unbranch() methods. Branches can point to either another
+ * steps array or a single step. The first branch in a group that hasn't been
+ * skipped (see branch()) is included by default (if $defaultBranch = true).
  *
  * @var array
  * @access public
@@ -182,19 +198,6 @@ class WizardComponent extends Component {
  */
 	protected $_wizardUrl = array();
 
-/**
- * WizardComponent Constructor
- *
- * @param ComponentCollection $collection A ComponentCollection this component can use to lazy-load its components
- * @param array               $settings   Array of configuration settings
- *
- * @access public
- */
-	public function __construct(ComponentCollection $collection, $settings = array()) {
-		parent::__construct($collection, $settings);
-		$this->_set($settings);
-	}
-
 /**
  * Initializes WizardComponent for use in the controller
  *
@@ -205,7 +208,11 @@ public function __construct(ComponentCollection $collection, $settings = array()
  */
 	public function initialize(Controller $controller) {
 		$this->controller = $controller;
-		$this->_sessionKey = $this->controller->Session->check('Wizard.complete') ? 'Wizard.complete' : 'Wizard.' . $controller->name;
+		if ($this->controller->Session->check('Wizard.complete')) {
+			$this->_sessionKey = 'Wizard.complete';
+		} else {
+			$this->_sessionKey = 'Wizard.' . $controller->name;
+		}
 		$this->_configKey = 'Wizard.config';
 		$this->_branchKey = 'Wizard.branches.' . $controller->name;
 	}
@@ -293,7 +300,6 @@ protected function _branchType($branch) {
  */
 	public function config($name, $value = null) {
 		if ($value == null) {
-			// $this->controller->Session->read("$this->_configKey")
 			return $this->controller->Session->read("$this->_configKey.$name");
 		}
 		$this->controller->Session->write("$this->_configKey.$name", $value);
@@ -311,14 +317,14 @@ public function config($name, $value = null) {
  */
 	public function process($step) {
 		if (isset($this->controller->request->data['Cancel'])) {
-			if (method_exists($this->controller, '_beforeCancel')) {
-				$this->controller->_beforeCancel($this->_getExpectedStep());
+			if (method_exists($this->controller, 'beforeCancel')) {
+				$this->controller->beforeCancel($this->_getExpectedStep());
 			}
 			$this->reset();
-			$this->controller->redirect($this->cancelUrl);
+			return $this->controller->redirect($this->cancelUrl);
 		}
 		if (isset($this->controller->request->data['Draft'])) {
-			if (method_exists($this->controller, '_saveDraft')) {
+			if (method_exists($this->controller, 'saveDraft')) {
 				$draft = array(
 					'_draft' => array(
 						'current' => array(
@@ -327,18 +333,18 @@ public function process($step) {
 						)
 					)
 				);
-				$this->controller->_saveDraft(array_merge_recursive((array)$this->read(), $draft));
+				$this->controller->saveDraft(array_merge_recursive((array)$this->read(), $draft));
 			}
 			$this->reset();
-			$this->controller->redirect($this->draftUrl);
+			return $this->controller->redirect($this->draftUrl);
 		}
 		if (empty($step)) {
 			if ($this->controller->Session->check('Wizard.complete')) {
-				if (method_exists($this->controller, '_afterComplete')) {
-					$this->controller->_afterComplete();
+				if (method_exists($this->controller, 'afterComplete')) {
+					$this->controller->afterComplete();
 				}
 				$this->reset();
-				$this->controller->redirect($this->completeUrl);
+				return $this->controller->redirect($this->completeUrl);
 			}
 			$this->autoReset = false;
 		} elseif ($step == 'reset') {
@@ -348,8 +354,8 @@ public function process($step) {
 		} else {
 			if ($this->_validStep($step)) {
 				$this->_setCurrentStep($step);
-				if (!empty($this->controller->data) && !isset($this->controller->request->data['Previous'])) {
-					$processCallback = '_' . Inflector::variable('process_' . $this->_currentStep);
+				if (!empty($this->controller->request->data) && !isset($this->controller->request->data['Previous'])) {
+					$processCallback = Inflector::variable('process_' . $this->_currentStep);
 					if (method_exists($this->controller, $processCallback)) {
 						$proceed = $this->controller->$processCallback();
 					} elseif ($this->autoValidate) {
@@ -360,28 +366,28 @@ public function process($step) {
 					if ($proceed) {
 						$this->save();
 						if (isset($this->controller->request->data['SaveAndBack']) && prev($this->steps)) {
-							$this->redirect(current($this->steps));
+							return $this->redirect(current($this->steps));
 						}
 						if (next($this->steps)) {
 							if ($this->autoAdvance) {
-								$this->redirect();
+								return $this->redirect();
 							}
-							$this->redirect(current($this->steps));
+							return $this->redirect(current($this->steps));
 						} else {
 							$this->controller->Session->write('Wizard.complete', $this->read());
 							$this->reset();
-							$this->controller->redirect(array('action' => $this->action));
+							return $this->controller->redirect(array('action' => $this->action));
 						}
 					}
 				} elseif (isset($this->controller->request->data['Previous']) && prev($this->steps)) {
-					$this->redirect(current($this->steps));
+					return $this->redirect(current($this->steps));
 				} elseif ($this->controller->Session->check("$this->_sessionKey._draft.current")) {
-					$this->controller->data = $this->read('_draft.current.data');
+					$this->controller->request->data = $this->read('_draft.current.data');
 					$this->controller->Session->delete("$this->_sessionKey._draft.current");
 				} elseif ($this->controller->Session->check("$this->_sessionKey.$this->_currentStep")) {
-					$this->controller->data = $this->read($this->_currentStep);
+					$this->controller->request->data = $this->read($this->_currentStep);
 				}
-				$prepareCallback = '_' . Inflector::variable('prepare_' . $this->_currentStep);
+				$prepareCallback = Inflector::variable('prepare_' . $this->_currentStep);
 				if (method_exists($this->controller, $prepareCallback)) {
 					$this->controller->$prepareCallback();
 				}
@@ -389,15 +395,18 @@ public function process($step) {
 				if ($this->nestedViews) {
 					$this->controller->viewPath .= '/' . $this->action;
 				}
-				return $this->controller->autoRender ? $this->controller->render($this->_currentStep) : true;
+				if ($this->controller->autoRender) {
+					return $this->controller->render($this->_currentStep);
+				}
+				return true;
 			} else {
-				$this->redirect();
+				return $this->redirect();
 			}
 		}
 		if ($step != 'reset' && $this->autoReset) {
 			$this->reset();
 		}
-		$this->redirect();
+		return $this->redirect();
 	}
 
 /**
@@ -442,7 +451,10 @@ public function read($key = null) {
 			return $this->controller->Session->read($this->_sessionKey);
 		} else {
 			$wizardData = $this->controller->Session->read("$this->_sessionKey.$key");
-			return !empty($wizardData) ? $wizardData : null;
+			if (!empty($wizardData)) {
+				return $wizardData;
+			}
+			return null;
 		}
 	}
 
@@ -510,7 +522,7 @@ protected function _validateData() {
  * Saves the data from the current step into the Session.
  *
  * Please note: This is normally called automatically by the component after
- * a successful _processCallback, but can be called directly for advanced navigation purposes.
+ * a successful processCallback, but can be called directly for advanced navigation purposes.
  *
  * @param string $step step key.
  * @param array $data  step details.
@@ -543,15 +555,15 @@ public function redirect($step = null, $status = null, $exit = true) {
 			$step = $this->_getExpectedStep();
 		}
 		$url = array(
-			'controller' => $this->controller->request->controller,
 			'action' => $this->action,
 			$step
 		);
-		$this->controller->redirect($url, $status, $exit);
+		return $this->controller->redirect($url, $status, $exit);
 	}
 
 /**
- * Selects a branch to be used in the steps array. The first branch in a group is included by default.
+ * Selects a branch to be used in the steps array. The first branch in a group
+ * is included by default.
  *
  * @param string  $name Branch name to be included in steps.
  * @param bool $skip Branch will be skipped instead of included if true.
@@ -564,10 +576,11 @@ public function branch($name, $skip = false) {
 		if ($this->controller->Session->check($this->_branchKey)) {
 			$branches = $this->controller->Session->read($this->_branchKey);
 		}
-		if (isset($branches[$name])) {
-			unset($branches[$name]);
+		if ($skip) {
+			$value = 'skip';
+		} else {
+			$value = 'branch';
 		}
-		$value = $skip ? 'skip' : 'branch';
 		$branches[$name] = $value;
 		$this->controller->Session->write($this->_branchKey, $branches);
 	}
@@ -575,7 +588,7 @@ public function branch($name, $skip = false) {
 /**
  * Loads previous draft session.
  *
- * @param array $draft Session data of same format passed to Controller::_saveDraft()
+ * @param array $draft Session data of same format passed to Controller::saveDraft()
  *
  * @see    WizardComponent::process()
  * @access public
@@ -584,9 +597,9 @@ public function branch($name, $skip = false) {
 	public function loadDraft($draft = array()) {
 		if (!empty($draft['_draft']['current']['step'])) {
 			$this->restore($draft);
-			$this->redirect($draft['_draft']['current']['step']);
+			return $this->redirect($draft['_draft']['current']['step']);
 		}
-		$this->redirect();
+		return $this->redirect();
 	}
 
 /**
diff --git a/README3.md b/README3.md
index fb2dd2d..278f748 100644
--- a/README3.md
+++ b/README3.md
@@ -23,11 +23,11 @@ An example showing how to retrieve all the current data with read() will be give
 
 One of my goals when writing this component was to prevent double submission of user data. One of the ways I accomplished this was by using the process callbacks for each step and redirecting to rather than rendering the next step.
 
-The second way was including an extra redirect and callback during the wizard completion process that creates a sort of "no man's land" for the wizard data. The way this works is, after the process callback for the last step is completed, the wizard data is moved to a new location in the session (Wizard.complete), the wizard redirects to a null step and another callback is called: _afterComplete(). 
+The second way was including an extra redirect and callback during the wizard completion process that creates a sort of "no man's land" for the wizard data. The way this works is, after the process callback for the last step is completed, the wizard data is moved to a new location in the session (Wizard.complete), the wizard redirects to a null step and another callback is called: afterComplete(). 
 
-_afterComplete() is an optional callback and is the ideal place to manipulate/store data after the wizard has been completed by the user. The callback does not need to return anything and the component automatically redirects to the $completeUrl (default '/') after the callback is finished.
+afterComplete() is an optional callback and is the ideal place to manipulate/store data after the wizard has been completed by the user. The callback does not need to return anything and the component automatically redirects to the $completeUrl (default '/') after the callback is finished.
 
-It's important to note that immediately after the afterComplete() callback and before the user is redirected to $completeUrl, the wizard is reset completely (all data is flushed from the session). If you need to redirect manually from _afterComplete(), be sure to call Wizard->reset() manually.
+It's important to note that immediately after the afterComplete() callback and before the user is redirected to $completeUrl, the wizard is reset completely (all data is flushed from the session). If you need to redirect manually from afterComplete(), be sure to call Wizard->reset() manually.
 
 So, to complete our tutorial example, we will pull all the data out of the wizard, store it in our database, and redirect the user to a confirmation page. 
 
@@ -52,7 +52,7 @@ class SignupController extends AppController {
 /**
  * [Wizard Process Callbacks]
  */
-	protected function _processAccount() {
+	public function processAccount() {
 		$this->Client->set($this->data);
 		$this->User->set($this->data);
 
@@ -62,7 +62,7 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	protected function _processAddress() {
+	public function processAddress() {
 		$this->Client->set($this->data);
 
 		if($this->Client->validates()) {
@@ -71,7 +71,7 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	protected function _processBilling() {
+	public function processBilling() {
 		$this->Billing->set($this->data);
 
 		if($this->Billing->validates()) {
@@ -80,13 +80,13 @@ class SignupController extends AppController {
 		return false;
 	}
 
-	protected function _processReview() {
+	public function processReview() {
 		return true;
 	}
 /**
  * [Wizard Completion Callback]
  */
-	protected function _afterComplete() {
+	protected function afterComplete() {
 		$wizardData = $this->Wizard->read();
 		extract($wizardData);
 
@@ -98,4 +98,4 @@ class SignupController extends AppController {
 }
 ?>
-Please note the addition to beforeFilter() and the new confirm() method. You would also need to create a view file (confirm.ctp) with something like "Congrats, your sign-up was successful!" etc. It would also be good to create some sort of token during the _afterComplete() callback and have it checked for in the confirm() method, but that's outside the scope of this tutorial. \ No newline at end of file +Please note the addition to beforeFilter() and the new confirm() method. You would also need to create a view file (confirm.ctp) with something like "Congrats, your sign-up was successful!" etc. It would also be good to create some sort of token during the afterComplete() callback and have it checked for in the confirm() method, but that's outside the scope of this tutorial. \ No newline at end of file diff --git a/Test/Case/AllWizardTest.php b/Test/Case/AllWizardTest.php new file mode 100644 index 0000000..8e2b5d6 --- /dev/null +++ b/Test/Case/AllWizardTest.php @@ -0,0 +1,9 @@ +addTestDirectoryRecursive(dirname(__FILE__) . DS . 'Controller'); + return $suite; + } +} diff --git a/Test/Case/Controller/Component/WizardComponentTest.php b/Test/Case/Controller/Component/WizardComponentTest.php new file mode 100644 index 0000000..2bbd55a --- /dev/null +++ b/Test/Case/Controller/Component/WizardComponentTest.php @@ -0,0 +1,526 @@ + array( + 'inList' => array( + 'rule' => array('inList', array('male', 'female')), + ), + ), + ); + +} + +/** + * AuthTestController class + * + * @package Wizard.Test.Case.Controller.Component + */ +class WizardTestController extends Controller { + + public $autoRender = false; + + public $uses = array('WizardUserMock'); + + public $components = array( + 'Session', + 'Wizard.Wizard' => array( + 'autoValidate' => true, + 'completeUrl' => array( + 'action' => 'wizard', + 'step1', + ), + 'steps' => array( + 'step1', + 'step2', + 'gender', // This step is autovalidated. + array( + 'male' => array('step3', 'step4'), + 'female' => array('step4', 'step5'), + 'unknown' => 'step6', + ), + 'confirmation', + ), + ), + ); + + public function wizard($step = null) { + $this->Wizard->process($step); + } + + public function processStep1() { + if (!empty($this->request->data)) { + return true; + } + return false; + } + + public function processStep2() { + if (!empty($this->request->data)) { + return true; + } + return false; + } + + public function processStep3() { + if (!empty($this->request->data)) { + return true; + } + return false; + } + + public function processStep4() { + if (!empty($this->request->data)) { + return true; + } + return false; + } + + public function processStep5() { + if (!empty($this->request->data)) { + return true; + } + return false; + } + + public function processConfirmation() { + return true; + } + + public function afterComplete() { + } + + public function redirect($url = null, $status = null, $exit = true) { + // Do not allow redirect() to exit in unit tests. + return parent::redirect($url, $status, false); + } +} +/** + * WizardComponentTest class + * + * @property WizardComponent $Wizard + * @package Wizard.Test.Case.Controller.Component + */ +class WizardComponentTest extends CakeTestCase { + +/** + * setUp method + * + * @return void + */ + public function setUp() { + parent::setUp(); + $CakeRequest = new CakeRequest(null, false); + $CakeResponse = $this->getMock('CakeResponse', array('send')); + $this->Controller = new WizardTestController($CakeRequest, $CakeResponse); + $ComponentCollection = new ComponentCollection(); + $ComponentCollection->init($this->Controller); + $this->Controller->Components->init($this->Controller); + $this->Wizard = $this->Controller->Wizard; + $this->Wizard->initialize($this->Controller); + } + +/** + * tearDown method + * + * @return void + */ + public function tearDown() { + parent::tearDown(); + $this->Wizard->Session->delete('Wizard'); + unset($this->Controller, $this->Wizard); + } + +/** + * Test WizardComponent::initialize(). + * + * @return void + */ + public function testInitialize() { + $this->assertTrue($this->Wizard->controller instanceof WizardTestController); + } + + public function testConfig() { + $steps = array('account', 'review'); + $result = $this->Wizard->config('steps', $steps); + $this->assertEquals($steps, $result); + + $configSteps = $this->Wizard->Session->read('Wizard.config.steps'); + $this->assertEquals($steps, $configSteps); + + $result = $this->Wizard->config('steps'); + $this->assertEquals($steps, $result); + } + + public function testBranch() { + $this->Wizard->branch('female'); + $expectedBranches = array( + 'WizardTest' => array( + 'female' => 'branch', + ), + ); + $sessionBranches = $this->Wizard->Session->read('Wizard.branches'); + $this->assertEquals($expectedBranches, $sessionBranches); + } + + public function testBranchSkip() { + $this->Wizard->branch('female', true); + $expectedBranches = array( + 'WizardTest' => array( + 'female' => 'skip', + ), + ); + $sessionBranches = $this->Wizard->Session->read('Wizard.branches'); + $this->assertEquals($expectedBranches, $sessionBranches); + } + + public function testBranchOverwrite() { + $this->Wizard->branch('male'); + $this->Wizard->branch('female'); + $expectedBranches = array( + 'WizardTest' => array( + 'male' => 'branch', + 'female' => 'branch', + ), + ); + $sessionBranches = $this->Wizard->Session->read('Wizard.branches'); + $this->assertEquals($expectedBranches, $sessionBranches); + + $this->Wizard->branch('male', true); + $expectedBranches = array( + 'WizardTest' => array( + 'male' => 'skip', + 'female' => 'branch', + ), + ); + $sessionBranches = $this->Wizard->Session->read('Wizard.branches'); + $this->assertEquals($expectedBranches, $sessionBranches); + } + + public function testStartup() { + $configAction = $this->Wizard->Session->read('Wizard.config.action'); + $this->assertEmpty($configAction); + $configSteps = $this->Wizard->Session->read('Wizard.config.steps'); + $this->assertEmpty($configSteps); + $this->assertEmpty($this->Wizard->controller->helpers); + + $this->Wizard->startup($this->Controller); + + $expectedAction = 'wizard'; + $resultAction = $this->Wizard->Session->read('Wizard.config.action'); + $this->assertEquals($expectedAction, $resultAction); + $expectedSteps = array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ); + $resultSteps = $this->Wizard->Session->read('Wizard.config.steps'); + $this->assertEquals($expectedSteps, $resultSteps); + $this->assertEquals($expectedSteps, $this->Wizard->steps); + $expectedHelpers = array( + 'Wizard.Wizard', + ); + $this->assertEquals($expectedHelpers, $this->Wizard->controller->helpers); + } + + public function testStartupSkipBranch() { + $configSteps = $this->Wizard->Session->read('Wizard.config.steps'); + $this->assertEmpty($configSteps); + + $this->Wizard->branch('male', true); + $this->Wizard->branch('female', true); + $this->Wizard->startup($this->Controller); + + $expectedSteps = array( + 'step1', + 'step2', + 'gender', + 'step6', + 'confirmation', + ); + $resultSteps = $this->Wizard->Session->read('Wizard.config.steps'); + $this->assertEquals($expectedSteps, $resultSteps); + $this->assertEquals($expectedSteps, $this->Wizard->steps); + } + + public function testStartupBranch() { + $configSteps = $this->Wizard->Session->read('Wizard.config.steps'); + $this->assertEmpty($configSteps); + + $this->Wizard->branch('female'); + $this->Wizard->startup($this->Controller); + + $expectedSteps = array( + 'step1', + 'step2', + 'gender', + 'step4', + 'step5', + 'confirmation', + ); + $resultSteps = $this->Wizard->Session->read('Wizard.config.steps'); + $this->assertEquals($expectedSteps, $resultSteps); + $this->assertEquals($expectedSteps, $this->Wizard->steps); + } + + public function testProcessStepOneGet() { + $session = $this->Wizard->Session->read('Wizard'); + $this->assertEmpty($session); + + $this->Wizard->startup($this->Controller); + $result = $this->Wizard->process('step1'); + $this->assertTrue($result); + + $expectedSession = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'step1', + 'activeStep' => 'step1', + ), + ); + $resultSession = $this->Wizard->Session->read('Wizard'); + $this->assertEquals($expectedSession, $resultSession); + } + + public function testProcessStepOnePost() { + $session = $this->Wizard->Session->read('Wizard'); + $this->assertEmpty($session); + $this->Wizard->startup($this->Controller); + // Emulate GET request to set session variables. + $this->Wizard->process('step1'); + // Emulate POST request. + $postData = array( + 'User' => array( + 'username' => 'admin', + 'password' => 'pass', + ), + ); + $this->Wizard->controller->request->data = $postData; + $CakeResponse = $this->Wizard->process('step1'); + + $this->assertInstanceOf('CakeResponse', $CakeResponse); + $headers = $CakeResponse->header(); + $this->assertContains('/wizard/step2', $headers['Location']); + + $expectedSession = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'step2', + 'activeStep' => 'step1', + ), + 'WizardTest' => array( + 'step1' => $postData, + ), + ); + $resultSession = $this->Wizard->Session->read('Wizard'); + $this->assertEquals($expectedSession, $resultSession); + } + + public function testProcessAutovalidatePost() { + // Set session prerequisites. + $session = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'gender', + 'activeStep' => 'gender', + ), + 'WizardTest' => array( + 'step1' => array(), + 'step2' => array(), + ), + ); + $this->Wizard->Session->write('Wizard', $session); + + $this->Wizard->startup($this->Controller); + $postData = array( + 'WizardUserMock' => array( + 'gender' => 'male', + ), + ); + $this->Wizard->controller->request->data = $postData; + $CakeResponse = $this->Wizard->process('gender'); + + $this->assertInstanceOf('CakeResponse', $CakeResponse); + $headers = $CakeResponse->header(); + $this->assertContains('/wizard/step3', $headers['Location']); + + $expectedSession = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'step3', + 'activeStep' => 'gender', + ), + 'WizardTest' => array( + 'step1' => array(), + 'step2' => array(), + 'gender' => $postData, + ), + ); + $resultSession = $this->Wizard->Session->read('Wizard'); + $this->assertEquals($expectedSession, $resultSession); + } + + public function testProcessLastStepPost() { + // Set session prerequisites. + $session = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'confirmation', + 'activeStep' => 'confirmation', + ), + 'WizardTest' => array( + 'step1' => array(), + 'step2' => array(), + 'gender' => array(), + 'step3' => array(), + 'step4' => array(), + ), + ); + $this->Wizard->Session->write('Wizard', $session); + + $this->Wizard->startup($this->Controller); + $postData = array( + 'WizardUserMock' => array( + 'confirm' => '1', + ), + ); + $this->Wizard->controller->request->data = $postData; + $CakeResponse = $this->Wizard->process('confirmation'); + + $this->assertInstanceOf('CakeResponse', $CakeResponse); + $headers = $CakeResponse->header(); + $this->assertContains('/wizard', $headers['Location']); + + $expectedSession = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'confirmation', + 'activeStep' => 'confirmation', + ), + 'complete' => array( + 'step1' => array(), + 'step2' => array(), + 'gender' => array(), + 'step3' => array(), + 'step4' => array(), + 'confirmation' => $postData, + ), + ); + $resultSession = $this->Wizard->Session->read('Wizard'); + $this->assertEquals($expectedSession, $resultSession); + } + + public function testProcessAfterComplete() { + // Set session prerequisites. + $session = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'confirmation', + 'activeStep' => 'confirmation', + ), + 'complete' => array( + 'step1' => array(), + 'step2' => array(), + 'gender' => array(), + 'step3' => array(), + 'step4' => array(), + 'confirmation' => array(), + ), + ); + $this->Wizard->Session->write('Wizard', $session); + + $this->Wizard->initialize($this->Controller); + $this->Wizard->startup($this->Controller); + $CakeResponse = $this->Wizard->process(null); + + $this->assertInstanceOf('CakeResponse', $CakeResponse); + $headers = $CakeResponse->header(); + $this->assertContains('/wizard/step1', $headers['Location']); + + $expectedSession = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'confirmation', + 'activeStep' => 'confirmation', + ), + ); + $resultSession = $this->Wizard->Session->read('Wizard'); + $this->assertEquals($expectedSession, $resultSession); + } +} diff --git a/phpunit-clover.xml b/phpunit-clover.xml new file mode 100644 index 0000000..3831142 --- /dev/null +++ b/phpunit-clover.xml @@ -0,0 +1,20 @@ + + + + + Controller + Model + View + + Cake + Composer + Test + *Test.php + + + + + + + + From ea7901cab4dc2ff8376552af4e58586146619032 Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Fri, 2 Dec 2016 10:42:12 +0100 Subject: [PATCH 43/48] more unit tests, bugs fixed (#19) * updated gitignore * updated gitignore and composer * improved WizardHelper code style * fixed WizardComponent code style * added .travis.yml and a test file * improved travis config, removed wrong import * added phpcs to travis config * improved travis config * fixed travis phpcs path * improved documentation * fixed code style * improved unit test * fixed testInitialize() * fixed unit tests, added code coverage * adjusted coverage config * temporary commented out unused test code * added unit test, adjusted coverage config * adjusted unit tests * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage configuration * adjusted coverage configuration * improved unit test * improved documentation and code style, more unit testing * fixed typo * fixed undefined variable * fixed unit test * branch() method simplified and unit tested * improved code style * more unit tests * more unit tests * fixed unit test * fixed unit test * fixed unit test * adjusted codecov.io call * adjusted codecov call * improved unit test * improved unit test * more unit tests * fixed unit test * more unit tests * improved unit tests * improved unit tests * fixed unit tests * improved unit tests * improved unit tests * improved unit tests * simplified unit tests * improved unit tests * temporary comment out yet unused code * more unit tests * improved code style and unit tests * improved unit test * fixed unit test * improved code style, unit tests * fixed unit test * fixed $this->controller->data, improved unit tests * fixed unit test * improved unit test * unit tests * unit tests * more debug * unit tests * unit tests * more debug * more debug * more debug * more debug * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * code style, unit tests * fixed unit tests * more unit tests * added missing import * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * more unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * reenable stylecheck * fixed code style * more unit tests * fixed code style * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * adjusted travis config --- .travis.yml | 1 - Test/Case/AllWizardTest.php | 1 + Test/Case/View/Helper/WizardHelperTest.php | 159 +++++++++++++++++++++ View/Helper/WizardHelper.php | 19 ++- 4 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 Test/Case/View/Helper/WizardHelperTest.php diff --git a/.travis.yml b/.travis.yml index c9b0285..98aa0fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,7 +83,6 @@ script: - ./lib/Cake/Console/cake test Wizard AllWizard --configuration=./plugins/Wizard/phpunit-clover.xml --stderr after_success: -# - bash <(curl -s https://codecov.io/bash) - cd ./plugins/Wizard && bash <(curl -s https://codecov.io/bash) notifications: diff --git a/Test/Case/AllWizardTest.php b/Test/Case/AllWizardTest.php index 8e2b5d6..3813712 100644 --- a/Test/Case/AllWizardTest.php +++ b/Test/Case/AllWizardTest.php @@ -4,6 +4,7 @@ class AllWizardTest extends CakeTestSuite { public static function suite() { $suite = new CakeTestSuite('All Wizard tests'); $suite->addTestDirectoryRecursive(dirname(__FILE__) . DS . 'Controller'); + $suite->addTestDirectoryRecursive(dirname(__FILE__) . DS . 'View'); return $suite; } } diff --git a/Test/Case/View/Helper/WizardHelperTest.php b/Test/Case/View/Helper/WizardHelperTest.php new file mode 100644 index 0000000..28fb5d2 --- /dev/null +++ b/Test/Case/View/Helper/WizardHelperTest.php @@ -0,0 +1,159 @@ +Wizard = new WizardHelper($View); + $session = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'gender', + 'activeStep' => 'gender', + ), + ); + CakeSession::write('Wizard', $session); + } + +/** + * tearDown method + * + * @return void + */ + public function tearDown() { + unset($this->Wizard); + CakeSession::delete('Wizard'); + parent::tearDown(); + } + + public function testConfigEmpty() { + CakeSession::delete('Wizard'); + $result = $this->Wizard->config('steps'); + $this->assertNull($result); + } + + public function testConfigReadAll() { + $expected = array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'gender', + 'activeStep' => 'gender', + ); + $result = $this->Wizard->config(); + $this->assertEquals($expected, $result); + } + + public function testConfigReadOne() { + $expected = array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ); + $result = $this->Wizard->config('steps'); + $this->assertEquals($expected, $result); + } + + public function testLink() { + $expected = 'gender'; + $result = $this->Wizard->link('gender'); + $this->assertEquals($expected, $result); + } + + public function testLinkStep() { + $expected = 'Gender'; + $result = $this->Wizard->link('Gender', 'gender'); + $this->assertEquals($expected, $result); + } + + public function testStepNumberCurrent() { + $result = $this->Wizard->stepNumber(); + $this->assertEquals(3, $result); + } + + public function testStepNumberConfirmation() { + $result = $this->Wizard->stepNumber('confirmation'); + $this->assertEquals(6, $result); + } + + public function testStepNumberNone() { + $result = $this->Wizard->stepNumber('step5'); + $this->assertFalse($result); + } + + public function testStepTotal() { + $result = $this->Wizard->stepTotal(); + $this->assertEquals(6, $result); + } + + public function testProgressMenu() { + $expected = ''; + $expected .= ''; + $expected .= ''; + $expected .= '
step3
'; + $expected .= '
step4
'; + $expected .= '
confirmation
'; + $result = $this->Wizard->progressMenu(); + $this->assertEquals($expected, $result); + } + + public function testProgressMenuCustomWrapper() { + $expected = '
  • step1
  • '; + $expected .= '
  • step2
  • '; + $expected .= '
  • gender
  • '; + $expected .= '
  • step3
  • '; + $expected .= '
  • step4
  • '; + $expected .= '
  • confirmation
  • '; + $result = $this->Wizard->progressMenu(array(), array('wrap' => 'li')); + $this->assertEquals($expected, $result); + } + + public function testProgressMenuCustomTitles() { + $expected = ''; + $expected .= ''; + $expected .= ''; + $expected .= '
    Shipping Address
    '; + $expected .= '
    Payment
    '; + $expected .= '
    Confirmation
    '; + + $titles = array( + 'step1' => 'Credentials', + 'step2' => 'Address', + 'gender' => 'Gender', + 'step3' => 'Shipping Address', + 'step4' => 'Payment', + 'confirmation' => 'Confirmation', + ); + $result = $this->Wizard->progressMenu($titles); + $this->assertEquals($expected, $result); + } +} diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php index 371edf5..5d4ac6b 100644 --- a/View/Helper/WizardHelper.php +++ b/View/Helper/WizardHelper.php @@ -55,8 +55,11 @@ public function link($title, $step = null, $htmlAttributes = array(), $confirmMe if ($step == null) { $step = $title; } - $wizardAction = $this->config('wizardAction'); - return $this->Html->link($title, $wizardAction . $step, $htmlAttributes, $confirmMessage); + $url = array( + 'action' => $this->config('action'), + $step, + ); + return $this->Html->link($title, $url, $htmlAttributes, $confirmMessage); } /** @@ -102,12 +105,16 @@ public function stepTotal() { public function progressMenu($titles = array(), $attributes = array(), $htmlAttributes = array(), $confirmMessage = false) { $wizardConfig = $this->config(); extract($wizardConfig); - $wizardAction = $this->config('wizardAction'); + $wizardAction = $this->config('action'); $attributes = array_merge(array('wrap' => 'div'), $attributes); extract($attributes); $incomplete = null; foreach ($steps as $title => $step) { - $title = empty($titles[$step]) ? $step : $titles[$step]; + if (empty($titles[$step])) { + $title = $step; + } else { + $title = $titles[$step]; + } if (!$incomplete) { if ($step == $expectedStep) { $incomplete = true; @@ -118,12 +125,12 @@ public function progressMenu($titles = array(), $attributes = array(), $htmlAttr if ($step == $activeStep) { $class .= ' active'; } - $this->output .= "<$wrap class='$class'>" . $this->Html->link($title, array( + $this->output .= "<$wrap class=\"$class\">" . $this->Html->link($title, array( 'action' => $wizardAction, $step ), $htmlAttributes, $confirmMessage) . ""; } else { - $this->output .= "<$wrap class='incomplete'>" . $title . ""; + $this->output .= "<$wrap class=\"incomplete\">" . $title . ""; } } return $this->output; From e5337f2269af4f9b2ba36ffeb17af273dc639e28 Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Sat, 10 Dec 2016 04:27:24 +0100 Subject: [PATCH 44/48] Progress menu links made consistent and humanized (#20) * updated gitignore * updated gitignore and composer * improved WizardHelper code style * fixed WizardComponent code style * added .travis.yml and a test file * improved travis config, removed wrong import * added phpcs to travis config * improved travis config * fixed travis phpcs path * improved documentation * fixed code style * improved unit test * fixed testInitialize() * fixed unit tests, added code coverage * adjusted coverage config * temporary commented out unused test code * added unit test, adjusted coverage config * adjusted unit tests * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage configuration * adjusted coverage configuration * improved unit test * improved documentation and code style, more unit testing * fixed typo * fixed undefined variable * fixed unit test * branch() method simplified and unit tested * improved code style * more unit tests * more unit tests * fixed unit test * fixed unit test * fixed unit test * adjusted codecov.io call * adjusted codecov call * improved unit test * improved unit test * more unit tests * fixed unit test * more unit tests * improved unit tests * improved unit tests * fixed unit tests * improved unit tests * improved unit tests * improved unit tests * simplified unit tests * improved unit tests * temporary comment out yet unused code * more unit tests * improved code style and unit tests * improved unit test * fixed unit test * improved code style, unit tests * fixed unit test * fixed $this->controller->data, improved unit tests * fixed unit test * improved unit test * unit tests * unit tests * more debug * unit tests * unit tests * more debug * more debug * more debug * more debug * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * code style, unit tests * fixed unit tests * more unit tests * added missing import * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * more unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * reenable stylecheck * fixed code style * more unit tests * fixed code style * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * adjusted travis config * Progress menu links made consistent * humanized steps in the helper * improved redirect link * improved redirect link --- Controller/Component/WizardComponent.php | 1 + Test/Case/View/Helper/WizardHelperTest.php | 30 +++++++++++----------- View/Helper/WizardHelper.php | 7 ++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php index 87980a6..fe1a3e5 100644 --- a/Controller/Component/WizardComponent.php +++ b/Controller/Component/WizardComponent.php @@ -555,6 +555,7 @@ public function redirect($step = null, $status = null, $exit = true) { $step = $this->_getExpectedStep(); } $url = array( + 'controller' => Inflector::underscore($this->controller->name), 'action' => $this->action, $step ); diff --git a/Test/Case/View/Helper/WizardHelperTest.php b/Test/Case/View/Helper/WizardHelperTest.php index 28fb5d2..4ec21d7 100644 --- a/Test/Case/View/Helper/WizardHelperTest.php +++ b/Test/Case/View/Helper/WizardHelperTest.php @@ -116,23 +116,23 @@ public function testStepTotal() { } public function testProgressMenu() { - $expected = ''; - $expected .= ''; - $expected .= ''; - $expected .= '
    step3
    '; - $expected .= '
    step4
    '; - $expected .= '
    confirmation
    '; + $expected = ''; + $expected .= ''; + $expected .= ''; + $expected .= ''; + $expected .= ''; + $expected .= ''; $result = $this->Wizard->progressMenu(); $this->assertEquals($expected, $result); } public function testProgressMenuCustomWrapper() { - $expected = '
  • step1
  • '; - $expected .= '
  • step2
  • '; - $expected .= '
  • gender
  • '; - $expected .= '
  • step3
  • '; - $expected .= '
  • step4
  • '; - $expected .= '
  • confirmation
  • '; + $expected = '
  • Step1
  • '; + $expected .= '
  • Step2
  • '; + $expected .= '
  • Gender
  • '; + $expected .= '
  • Step3
  • '; + $expected .= '
  • Step4
  • '; + $expected .= '
  • Confirmation
  • '; $result = $this->Wizard->progressMenu(array(), array('wrap' => 'li')); $this->assertEquals($expected, $result); } @@ -141,9 +141,9 @@ public function testProgressMenuCustomTitles() { $expected = ''; $expected .= ''; $expected .= ''; - $expected .= '
    Shipping Address
    '; - $expected .= '
    Payment
    '; - $expected .= '
    Confirmation
    '; + $expected .= ''; + $expected .= ''; + $expected .= ''; $titles = array( 'step1' => 'Credentials', diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php index 5d4ac6b..728a43b 100644 --- a/View/Helper/WizardHelper.php +++ b/View/Helper/WizardHelper.php @@ -95,7 +95,8 @@ public function stepTotal() { * Returns a set of html elements containing links for each step in the wizard. * * @param array|string $titles Array of form steps where the keys are - * the steps and the values are the titles to be used for links. + * the steps and the values are the titles to be used for links. If empty then humanized + * step names are used from session. * @param array|string $attributes pass a value for 'wrap' to change the default tag used * @param array|string $htmlAttributes Array of options and HTML attributes. * @param bool|string $confirmMessage JavaScript confirmation message. This @@ -111,7 +112,7 @@ public function progressMenu($titles = array(), $attributes = array(), $htmlAttr $incomplete = null; foreach ($steps as $title => $step) { if (empty($titles[$step])) { - $title = $step; + $title = Inflector::humanize($step); } else { $title = $titles[$step]; } @@ -130,7 +131,7 @@ public function progressMenu($titles = array(), $attributes = array(), $htmlAttr $step ), $htmlAttributes, $confirmMessage) . ""; } else { - $this->output .= "<$wrap class=\"incomplete\">" . $title . ""; + $this->output .= "<$wrap class=\"incomplete\">$title"; } } return $this->output; From a8981164fdd8e7e800041d0abb7879700572f5d5 Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Mon, 27 Feb 2017 20:32:40 +0100 Subject: [PATCH 45/48] Wizard root session key is refactored to a configurable component variable (#22) * Wizard root session key is refactored to a configurable component variable. * fixed unit test * Moved sessionRootKey related code from initialize to startup to ease the configuration in a controller. * Setting session keys refactored to a private method. * Fixed variable name. --- Controller/Component/WizardComponent.php | 35 ++++++++++++++----- .../Component/WizardComponentTest.php | 32 ++++++++++++++++- View/Helper/WizardHelper.php | 11 ++++-- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php index fe1a3e5..735d265 100644 --- a/Controller/Component/WizardComponent.php +++ b/Controller/Component/WizardComponent.php @@ -156,6 +156,13 @@ class WizardComponent extends Component { */ public $nestedViews = false; +/** + * Holds the root of the session key for data storage. + * + * @var string + */ + public $sessionRootKey = 'Wizard'; + /** * Other components used. * @@ -208,13 +215,22 @@ class WizardComponent extends Component { */ public function initialize(Controller $controller) { $this->controller = $controller; - if ($this->controller->Session->check('Wizard.complete')) { - $this->_sessionKey = 'Wizard.complete'; + $this->__setSessionKeys(); + } + +/** + * Sets session keys used by this component. + * + * @return void + */ + private function __setSessionKeys() { + if ($this->controller->Session->check($this->sessionRootKey . '.complete')) { + $this->_sessionKey = $this->sessionRootKey . '.complete'; } else { - $this->_sessionKey = 'Wizard.' . $controller->name; + $this->_sessionKey = $this->sessionRootKey . '.' . $this->controller->name; } - $this->_configKey = 'Wizard.config'; - $this->_branchKey = 'Wizard.branches.' . $controller->name; + $this->_configKey = $this->sessionRootKey . '.config'; + $this->_branchKey = $this->sessionRootKey . '.branches.' . $this->controller->name; } /** @@ -227,11 +243,14 @@ public function initialize(Controller $controller) { * @return void */ public function startup(Controller $controller) { + $this->__setSessionKeys(); $this->steps = $this->_parseSteps($this->steps); $this->config('action', $this->action); $this->config('steps', $this->steps); if (!in_array('Wizard.Wizard', $this->controller->helpers) && !array_key_exists('Wizard.Wizard', $this->controller->helpers)) { - $this->controller->helpers[] = 'Wizard.Wizard'; + $this->controller->helpers['Wizard.Wizard'] = array( + 'sessionRootKey' => $this->sessionRootKey, + ); } } @@ -339,7 +358,7 @@ public function process($step) { return $this->controller->redirect($this->draftUrl); } if (empty($step)) { - if ($this->controller->Session->check('Wizard.complete')) { + if ($this->controller->Session->check($this->sessionRootKey . '.complete')) { if (method_exists($this->controller, 'afterComplete')) { $this->controller->afterComplete(); } @@ -374,7 +393,7 @@ public function process($step) { } return $this->redirect(current($this->steps)); } else { - $this->controller->Session->write('Wizard.complete', $this->read()); + $this->controller->Session->write($this->sessionRootKey . '.complete', $this->read()); $this->reset(); return $this->controller->redirect(array('action' => $this->action)); } diff --git a/Test/Case/Controller/Component/WizardComponentTest.php b/Test/Case/Controller/Component/WizardComponentTest.php index 2bbd55a..01e0034 100644 --- a/Test/Case/Controller/Component/WizardComponentTest.php +++ b/Test/Case/Controller/Component/WizardComponentTest.php @@ -230,7 +230,7 @@ public function testStartup() { $this->assertEquals($expectedSteps, $resultSteps); $this->assertEquals($expectedSteps, $this->Wizard->steps); $expectedHelpers = array( - 'Wizard.Wizard', + 'Wizard.Wizard' => array('sessionRootKey' => 'Wizard'), ); $this->assertEquals($expectedHelpers, $this->Wizard->controller->helpers); } @@ -275,6 +275,36 @@ public function testStartupBranch() { $this->assertEquals($expectedSteps, $this->Wizard->steps); } + public function testStartupCustomRootSessionKey() { + $configAction = $this->Wizard->Session->read('WizardInstance001.config.action'); + $this->assertEmpty($configAction); + $configSteps = $this->Wizard->Session->read('WizardInstance001.config.steps'); + $this->assertEmpty($configSteps); + $this->assertEmpty($this->Wizard->controller->helpers); + + $this->Wizard->sessionRootKey = 'WizardInstance001'; + $this->Wizard->startup($this->Controller); + + $expectedAction = 'wizard'; + $resultAction = $this->Wizard->Session->read('WizardInstance001.config.action'); + $this->assertEquals($expectedAction, $resultAction); + $expectedSteps = array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ); + $resultSteps = $this->Wizard->Session->read('WizardInstance001.config.steps'); + $this->assertEquals($expectedSteps, $resultSteps); + $this->assertEquals($expectedSteps, $this->Wizard->steps); + $expectedHelpers = array( + 'Wizard.Wizard' => array('sessionRootKey' => 'WizardInstance001'), + ); + $this->assertEquals($expectedHelpers, $this->Wizard->controller->helpers); + } + public function testProcessStepOneGet() { $session = $this->Wizard->Session->read('Wizard'); $this->assertEmpty($session); diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php index 728a43b..f3eab21 100644 --- a/View/Helper/WizardHelper.php +++ b/View/Helper/WizardHelper.php @@ -22,6 +22,13 @@ class WizardHelper extends AppHelper { public $output = null; +/** + * Holds the root of the session key for data storage. + * + * @var string + */ + public $sessionRootKey = 'Wizard'; + /** * undocumented function * @@ -30,9 +37,9 @@ class WizardHelper extends AppHelper { */ public function config($key = null) { if ($key == null) { - return $this->Session->read('Wizard.config'); + return $this->Session->read($this->sessionRootKey . '.config'); } else { - $wizardData = $this->Session->read('Wizard.config.' . $key); + $wizardData = $this->Session->read($this->sessionRootKey . '.config.' . $key); if (!empty($wizardData)) { return $wizardData; } else { From db8b6a43e0cabefc39b4d693a438f7bee5ec1d95 Mon Sep 17 00:00:00 2001 From: Manuel Lingureanu Date: Fri, 31 Mar 2017 23:39:55 +0300 Subject: [PATCH 46/48] Update WizardComponent.php test type for $proceed --- Controller/Component/WizardComponent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php index 735d265..9f80583 100644 --- a/Controller/Component/WizardComponent.php +++ b/Controller/Component/WizardComponent.php @@ -382,7 +382,7 @@ public function process($step) { } else { throw new NotImplementedException(sprintf(__('Process Callback not found. Please create Controller::%s', $processCallback))); } - if ($proceed) { + if ($proceed === true) { $this->save(); if (isset($this->controller->request->data['SaveAndBack']) && prev($this->steps)) { return $this->redirect(current($this->steps)); From 1484bfcf1b23d182e53619e610a1b21d57d62c83 Mon Sep 17 00:00:00 2001 From: Manuel Lingureanu Date: Tue, 11 Apr 2017 12:46:41 +0300 Subject: [PATCH 47/48] Update WizardComponent.php --- Controller/Component/WizardComponent.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php index 9f80583..be64e3f 100644 --- a/Controller/Component/WizardComponent.php +++ b/Controller/Component/WizardComponent.php @@ -377,12 +377,15 @@ public function process($step) { $processCallback = Inflector::variable('process_' . $this->_currentStep); if (method_exists($this->controller, $processCallback)) { $proceed = $this->controller->$processCallback(); + if (!is_bool($proceed)) { + throw new NotImplementedException(sprintf(__('Process Callback Controller::%s should return boolean', $processCallback))); + } } elseif ($this->autoValidate) { $proceed = $this->_validateData(); } else { throw new NotImplementedException(sprintf(__('Process Callback not found. Please create Controller::%s', $processCallback))); } - if ($proceed === true) { + if ($proceed) { $this->save(); if (isset($this->controller->request->data['SaveAndBack']) && prev($this->steps)) { return $this->redirect(current($this->steps)); From 89f0f3f893d05c5833ff7725923c7d28f2889b1f Mon Sep 17 00:00:00 2001 From: Val Bancer Date: Fri, 2 Jun 2017 13:52:18 +0200 Subject: [PATCH 48/48] Added persistUrlParams config option (#21) * updated gitignore * updated gitignore and composer * improved WizardHelper code style * fixed WizardComponent code style * added .travis.yml and a test file * improved travis config, removed wrong import * added phpcs to travis config * improved travis config * fixed travis phpcs path * improved documentation * fixed code style * improved unit test * fixed testInitialize() * fixed unit tests, added code coverage * adjusted coverage config * temporary commented out unused test code * added unit test, adjusted coverage config * adjusted unit tests * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage config * adjusted coverage configuration * adjusted coverage configuration * improved unit test * improved documentation and code style, more unit testing * fixed typo * fixed undefined variable * fixed unit test * branch() method simplified and unit tested * improved code style * more unit tests * more unit tests * fixed unit test * fixed unit test * fixed unit test * adjusted codecov.io call * adjusted codecov call * improved unit test * improved unit test * more unit tests * fixed unit test * more unit tests * improved unit tests * improved unit tests * fixed unit tests * improved unit tests * improved unit tests * improved unit tests * simplified unit tests * improved unit tests * temporary comment out yet unused code * more unit tests * improved code style and unit tests * improved unit test * fixed unit test * improved code style, unit tests * fixed unit test * fixed $this->controller->data, improved unit tests * fixed unit test * improved unit test * unit tests * unit tests * more debug * unit tests * unit tests * more debug * more debug * more debug * more debug * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * code style, unit tests * fixed unit tests * more unit tests * added missing import * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * more unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * reenable stylecheck * fixed code style * more unit tests * fixed code style * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * unit tests * adjusted travis config * Progress menu links made consistent * humanized steps in the helper * improved redirect link * improved redirect link * persist url params on redirect * added debug * more debug * removed debug * adjusted url in the unit test * more debug * fixed url parsing in unit test * removed debug * support persistUrlParams in the WizardHelper * improved persist url params logic * mode debug * minor improvement * fixed passed param in the url * removed debug, fixed unit tests * Wizard root session key is refactored to a configurable component variable. * fixed unit test * Moved sessionRootKey related code from initialize to startup to ease the configuration in a controller. * Setting session keys refactored to a private method. * Fixed variable name. * #more unit tests * adjusted unit tests * better clear session in tests * unit tests adjusted * unit tests * unit tests improved * Some refactoring in order to reduce the number of redirects * more refactoring * more docs and reset the expected step in branch() * updates expected step after save() and updates steps after unbranch() is called * ensure the current step is set properly * Fixed indefinite loop bug. * used Router::reverseToArray() * updated cakephp version * restored deleted params --- .travis.yml | 2 +- Controller/Component/WizardComponent.php | 54 +++++- .../Component/WizardComponentTest.php | 156 +++++++++++++++++- Test/Case/View/Helper/WizardHelperTest.php | 35 ++++ View/Helper/WizardHelper.php | 29 +++- 5 files changed, 263 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98aa0fe..d9998ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ php: env: # - CAKE_VERSION=2.8.0 DB=mysql - - CAKE_VERSION=2.9.2 DB=mysql + - CAKE_VERSION=2.9.9 DB=mysql install: - git clone git://github.com/cakephp/cakephp ../cakephp && cd ../cakephp && git checkout $CAKE_VERSION diff --git a/Controller/Component/WizardComponent.php b/Controller/Component/WizardComponent.php index be64e3f..a232bd3 100644 --- a/Controller/Component/WizardComponent.php +++ b/Controller/Component/WizardComponent.php @@ -121,6 +121,14 @@ class WizardComponent extends Component { */ public $draftUrl = '/'; +/** + * If `true` then URL parameters from the first step will be present in the URLs + * of all other steps. + * + * @var bool + */ + public $persistUrlParams = false; + /** * If true, the first "non-skipped" branch in a group will be used if a branch has * not been included specifically. @@ -205,6 +213,13 @@ class WizardComponent extends Component { */ protected $_wizardUrl = array(); +/** + * Holds the array with steps and branches from the initial Wizard configuration. + * + * @var array + */ + protected $_stepsAndBranches = array(); + /** * Initializes WizardComponent for use in the controller * @@ -216,6 +231,7 @@ class WizardComponent extends Component { public function initialize(Controller $controller) { $this->controller = $controller; $this->__setSessionKeys(); + $this->_stepsAndBranches = $this->steps; } /** @@ -244,9 +260,8 @@ private function __setSessionKeys() { */ public function startup(Controller $controller) { $this->__setSessionKeys(); - $this->steps = $this->_parseSteps($this->steps); $this->config('action', $this->action); - $this->config('steps', $this->steps); + $this->_configSteps($this->steps); if (!in_array('Wizard.Wizard', $this->controller->helpers) && !array_key_exists('Wizard.Wizard', $this->controller->helpers)) { $this->controller->helpers['Wizard.Wizard'] = array( 'sessionRootKey' => $this->sessionRootKey, @@ -254,6 +269,18 @@ public function startup(Controller $controller) { } } +/** + * Parses the steps array by stripping off nested arrays not included in the branches + * and writes a simple array with the correct steps to session. + * + * @param array $steps Array to be parsed for nested arrays. + * @return void + */ + protected function _configSteps($steps) { + $this->steps = $this->_parseSteps($steps); + $this->config('steps', $this->steps); + } + /** * Parses the steps array by stripping off nested arrays not included in the branches * and returns a simple array with the correct steps. @@ -513,6 +540,9 @@ protected function _validStep($step) { * @return void */ protected function _setCurrentStep($step) { + if (!in_array($step, $this->steps)) { + return; + } $this->_currentStep = reset($this->steps); while (current($this->steps) != $step) { $this->_currentStep = next($this->steps); @@ -559,6 +589,8 @@ public function save($step = null, $data = null) { $data = $this->controller->request->data; } $this->controller->Session->write("$this->_sessionKey.$step", $data); + $this->_getExpectedStep(); + $this->_setCurrentStep($step); } /** @@ -576,11 +608,17 @@ public function redirect($step = null, $status = null, $exit = true) { if ($step == null) { $step = $this->_getExpectedStep(); } - $url = array( - 'controller' => Inflector::underscore($this->controller->name), - 'action' => $this->action, - $step - ); + if ($this->persistUrlParams) { + $url = Router::reverseToArray($this->controller->request); + $url['action'] = $this->action; + $url[0] = $step; + } else { + $url = array( + 'controller' => Inflector::underscore($this->controller->name), + 'action' => $this->action, + $step, + ); + } return $this->controller->redirect($url, $status, $exit); } @@ -606,6 +644,7 @@ public function branch($name, $skip = false) { } $branches[$name] = $value; $this->controller->Session->write($this->_branchKey, $branches); + $this->_configSteps($this->_stepsAndBranches); } /** @@ -677,6 +716,7 @@ public function delete($key = null) { */ public function unbranch($branch) { $this->controller->Session->delete("$this->_branchKey.$branch"); + $this->_configSteps($this->_stepsAndBranches); } } diff --git a/Test/Case/Controller/Component/WizardComponentTest.php b/Test/Case/Controller/Component/WizardComponentTest.php index 01e0034..279d831 100644 --- a/Test/Case/Controller/Component/WizardComponentTest.php +++ b/Test/Case/Controller/Component/WizardComponentTest.php @@ -23,6 +23,7 @@ class WizardUserMock extends Model { /** * AuthTestController class * + * @property WizardComponent $Wizard * @package Wizard.Test.Case.Controller.Component */ class WizardTestController extends Controller { @@ -71,6 +72,22 @@ public function processStep2() { return false; } + public function processGender() { + if (!empty($this->request->data)) { + if ($this->Wizard->defaultBranch === false) { + if ($this->request->data['WizardUserMock']['gender'] == 'female') { + $this->Wizard->unbranch('male'); + $this->Wizard->branch('female'); + } else { + $this->Wizard->unbranch('female'); + $this->Wizard->branch('male'); + } + } + return true; + } + return false; + } + public function processStep3() { if (!empty($this->request->data)) { return true; @@ -136,7 +153,7 @@ public function setUp() { */ public function tearDown() { parent::tearDown(); - $this->Wizard->Session->delete('Wizard'); + CakeSession::destroy(); unset($this->Controller, $this->Wizard); } @@ -374,6 +391,87 @@ public function testProcessStepOnePost() { $this->assertEquals($expectedSession, $resultSession); } +/** + * Tests 'autoAdvance' and 'defaultBranch' settings set to false and manual call to `branch()`. + * + * @return void + */ + public function testProcessGenderPost() { + $this->Wizard->Session->delete('Wizard'); + unset($this->Controller, $this->Wizard); + $CakeRequest = new CakeRequest(null, false); + $CakeResponse = $this->getMock('CakeResponse', array('send')); + $this->Controller = new WizardTestController($CakeRequest, $CakeResponse); + $this->Controller->components['Wizard.Wizard']['autoAdvance'] = false; + $this->Controller->components['Wizard.Wizard']['defaultBranch'] = false; + $ComponentCollection = new ComponentCollection(); + $ComponentCollection->init($this->Controller); + $this->Controller->Components->init($this->Controller); + $this->Wizard = $this->Controller->Wizard; + $this->Wizard->initialize($this->Controller); + + // Set session prerequisites. + $session = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'gender', + 'activeStep' => 'gender', + ), + 'WizardTest' => array( + 'step1' => array(), + 'step2' => array(), + ), + ); + $this->Wizard->Session->write('Wizard', $session); + + $this->Wizard->startup($this->Controller); + $postData = array( + 'WizardUserMock' => array( + 'gender' => 'female', + ), + ); + $this->Wizard->controller->request->data = $postData; + $CakeResponse = $this->Wizard->process('gender'); + + $expectedSession = array( + 'branches' => array( + 'WizardTest' => array( + 'female' => 'branch', + ), + ), + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step4', + 'step5', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'step4', + 'activeStep' => 'gender', + ), + 'WizardTest' => array( + 'step1' => array(), + 'step2' => array(), + 'gender' => $postData, + ), + ); + $resultSession = $this->Wizard->Session->read('Wizard'); + $this->assertEquals($expectedSession, $resultSession); + + $this->assertInstanceOf('CakeResponse', $CakeResponse); + $headers = $CakeResponse->header(); + $this->assertContains('/wizard/step4', $headers['Location']); + } + public function testProcessAutovalidatePost() { // Set session prerequisites. $session = array( @@ -553,4 +651,60 @@ public function testProcessAfterComplete() { $resultSession = $this->Wizard->Session->read('Wizard'); $this->assertEquals($expectedSession, $resultSession); } + + public function testRedirectPersistUrlParams() { + $session = $this->Wizard->Session->read('Wizard'); + $this->assertEmpty($session); + + $url = '/wizard_test/wizard/step1/123/key:value?x=7&y=9'; + $CakeRequest = new CakeRequest($url, true); + $CakeRequest->addParams(Router::parse($url)); + $CakeResponse = $this->getMock('CakeResponse', array('send')); + $this->Controller = new WizardTestController($CakeRequest, $CakeResponse); + $this->Controller->components['Wizard.Wizard']['persistUrlParams'] = true; + $ComponentCollection = new ComponentCollection(); + $ComponentCollection->init($this->Controller); + $this->Controller->Components->init($this->Controller); + $this->Wizard = $this->Controller->Wizard; + $this->Wizard->initialize($this->Controller); + + $this->Wizard->startup($this->Controller); + //$this->Wizard->persistUrlParams = true; + // Emulate GET request to set session variables. + $this->Wizard->process('step1'); + // Emulate POST request. + $postData = array( + 'User' => array( + 'username' => 'admin', + 'password' => 'pass', + ), + ); + $this->Wizard->controller->request->data = $postData; + $CakeResponse = $this->Wizard->process('step1'); + + $this->assertInstanceOf('CakeResponse', $CakeResponse); + $headers = $CakeResponse->header(); + $this->assertContains('/wizard_test/wizard/step2/123/key:value?x=7&y=9', $headers['Location']); + + $expectedSession = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'step2', + 'activeStep' => 'step1', + ), + 'WizardTest' => array( + 'step1' => $postData, + ), + ); + $resultSession = $this->Wizard->Session->read('Wizard'); + $this->assertEquals($expectedSession, $resultSession); + } } diff --git a/Test/Case/View/Helper/WizardHelperTest.php b/Test/Case/View/Helper/WizardHelperTest.php index 4ec21d7..9689dd5 100644 --- a/Test/Case/View/Helper/WizardHelperTest.php +++ b/Test/Case/View/Helper/WizardHelperTest.php @@ -156,4 +156,39 @@ public function testProgressMenuCustomTitles() { $result = $this->Wizard->progressMenu($titles); $this->assertEquals($expected, $result); } + + public function testProgressMenuPersistUrlParams() { + $url = '/wizard_test/wizard/gender/123?x=7&y=9'; + $CakeRequest = new CakeRequest($url, true); + $CakeRequest->addParams(Router::parse($url)); + $Controller = new Controller($CakeRequest, new CakeResponse()); + $View = new View($Controller); + $this->Wizard = new WizardHelper($View); + $session = array( + 'config' => array( + 'steps' => array( + 'step1', + 'step2', + 'gender', + 'step3', + 'step4', + 'confirmation', + ), + 'action' => 'wizard', + 'expectedStep' => 'gender', + 'activeStep' => 'gender', + 'persistUrlParams' => true, + ), + ); + CakeSession::write('Wizard', $session); + + $expected = ''; + $expected .= ''; + $expected .= ''; + $expected .= ''; + $expected .= ''; + $expected .= ''; + $result = $this->Wizard->progressMenu(); + $this->assertEquals($expected, $result); + } } diff --git a/View/Helper/WizardHelper.php b/View/Helper/WizardHelper.php index f3eab21..234dab9 100644 --- a/View/Helper/WizardHelper.php +++ b/View/Helper/WizardHelper.php @@ -133,10 +133,10 @@ public function progressMenu($titles = array(), $attributes = array(), $htmlAttr if ($step == $activeStep) { $class .= ' active'; } - $this->output .= "<$wrap class=\"$class\">" . $this->Html->link($title, array( - 'action' => $wizardAction, - $step - ), $htmlAttributes, $confirmMessage) . ""; + $url = $this->__getStepUrl($step); + $this->output .= "<$wrap class=\"$class\">"; + $this->output .= $this->Html->link($title, $url, $htmlAttributes, $confirmMessage); + $this->output .= ""; } else { $this->output .= "<$wrap class=\"incomplete\">$title"; } @@ -158,4 +158,25 @@ public function create($model = null, $options = array()) { } return $this->Form->create($model, $options); } + +/** + * Constructs the URL for a given step. + * + * @param string $step step action. + * @return array + */ + private function __getStepUrl($step) { + $wizardAction = $this->config('action'); + if ($this->config('persistUrlParams')) { + $url = Router::reverseToArray($this->request); + $url['action'] = $this->action; + $url[0] = $step; + } else { + $url = array( + 'action' => $wizardAction, + $step, + ); + } + return $url; + } }