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 string * @access public */ var $completeUrl = '/'; /** * Url to be redirected to after 'Cancel' submit button has been pressed by user. * * @var string * @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; /** * 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) { if (!empty($this->wizardAction)) { $this->wizardAction .= '/'; } $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(__('Process Callback not found. Please create Controller::' . $processCallback, true), 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->render($this->_currentStep); } else { trigger_error(__('Step validation: ' . $step . ' is not a valid step.', true), 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 mixed $url Stepname to be redirected to. * @access public */ function redirect($step = null, $status = null, $exit = true) { if ($step == null) { $step = $this->_getExpectedStep(); } $url = $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; } } ?>