Source of file MultiFormStep.php
Size: 15,036 Bytes - Last Modified: 2021-12-23T10:33:19+00:00
/var/www/docs.ssmods.com/process/src/src/Models/MultiFormStep.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 | <?php namespace SilverStripe\MultiForm\Models; use SilverStripe\Control\Controller; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\Validator; use SilverStripe\ORM\DataObject; /** * MultiFormStep controls the behaviour of a single form step in the MultiForm * process. All form steps are required to be subclasses of this class, as it * encapsulates the functionality required for the step to be aware of itself * in the process by knowing what it's next step is, and if applicable, it's previous * step. * */ class MultiFormStep extends DataObject { private static $db = [ 'Data' => 'Text' // stores serialized maps with all session information ]; private static $has_one = [ 'Session' => MultiFormSession::class ]; private static $table_name = 'MultiFormStep'; /** * Centerpiece of the flow control for the form. * * If set to a string, you have a linear form flow * If set to an array, you should use {@link getNextStep()} * to enact flow control and branching to different form * steps, most likely based on previously set session data * (e.g. a checkbox field or a dropdown). * * @var array|string */ private static $next_steps; /** * Each {@link MultiForm} subclass needs at least * one step which is marked as the "final" one * and triggers the {@link MultiForm->finish()} * method that wraps up the whole submission. * * @var boolean */ private static $is_final_step = false; /** * This variable determines whether a user can use * the "back" action from this step. * * @TODO This does not check if the arbitrarily chosen step * using the step indicator is actually a previous step, so * unless you remove the link from the indicator template, or * type in StepID=23 to the address bar you can still go back * using the step indicator. * * @var boolean */ private static $can_go_back = true; /** * Title of this step. * * Used for the step indicator templates. * * @var string */ protected $title; /** * Form class that this step is directly related to. * * @var MultiForm subclass */ protected $form; /** * List of additional CSS classes for this step * * @var array $extraClasses */ protected $extraClasses = []; /** * Temporary cache to increase the performance for repeated look ups. * * @var array $cache */ protected $step_data_cache = []; /** * Form fields to be rendered with this step. * (Form object is created in {@link MultiForm}. * * This function needs to be implemented on your * subclasses of MultiFormStep. * * @return FieldList */ public function getFields() { user_error('Please implement getFields on your MultiFormStep subclass', E_USER_ERROR); } /** * Additional form actions to be added to this step. * (Form object is created in {@link MultiForm}. * * Note: This is optional, and is to be implemented * on your subclasses of MultiFormStep. * * @return FieldList */ public function getExtraActions() { return FieldList::create(); } /** * Get a validator specific to this form. * The form is automatically validated in {@link Form->httpSubmission()}. * * @return bool|Validator */ public function getValidator() { return false; } /** * Accessor method for $this->title * * @return string Title of this step */ public function getTitle() { return $this->title ? $this->title : get_class($this); } /** * Gets a direct link to this step (only works if you're allowed to skip * steps, or this step has already been saved to the database for the * current {@link MultiFormSession}). * * @return string Relative URL to this step */ public function Link() { $form = $this->form; return Controller::join_links( $form->getDisplayLink(), sprintf("?%s=%s&StepID=%s", $form->getGetVar(), $this->getSession()->Hash, $this->ID) ); } /** * Unserialize stored session data and return it. * This is used for loading data previously saved * in session back into the form. * * You need to overload this method onto your own * step if you require custom loading. An example * would be selective loading specific fields, leaving * others that are not required. * * @return array */ public function loadData() { return ($this->Data && is_string($this->Data)) ? unserialize($this->Data) : []; } /** * Save the data for this step into session, serializing it first. * * To selectively save fields, instead of it all, this * method would need to be overloaded on your step class. * * @param array $data The processed data from save() on {@link MultiForm} */ public function saveData($data) { $this->Data = serialize($data); $this->write(); } /** * Save the data on this step into an object, * similiar to {@link Form->saveInto()} - by building * a stub form from {@link getFields()}. This is necessary * to trigger each {@link FormField->saveInto()} method * individually, rather than assuming that all data * serialized through {@link saveData()} can be saved * as a simple value outside of the original FormField context. * * @param DataObject $obj * @return DataObject */ public function saveInto($obj) { $form = Form::create( Controller::curr(), 'Form', $this->getFields(), FieldList::create() ); $form->loadDataFrom($this->loadData()); $form->saveInto($obj); return $obj; } /** * Custom validation for a step. In most cases, it should be sufficient * to have built-in validation through the {@link Validator} class * on the {@link getValidator()} method. * * Use {@link Form->sessionMessage()} to feed back validation messages * to the user. Please don't redirect from this method, * this is taken care of in {@link next()}. * * @param array $data Request data * @param Form $form * @return boolean Validation success */ public function validateStep($data, $form) { return true; } /** * Returns the first value of $next_step * * @return string Classname of a {@link MultiFormStep} subclass */ public function getNextStep() { $nextSteps = $this->config()->get('next_steps'); // Check if next_steps have been implemented properly if not the final step if (!$this->isFinalStep()) { if (!isset($nextSteps)) { user_error( 'MultiFormStep->getNextStep(): Please define at least one $next_steps on ' . static::class, E_USER_ERROR ); } } if (is_string($nextSteps)) { return $nextSteps; } elseif (is_array($nextSteps) && count($nextSteps)) { // custom flow control goes here return $nextSteps[0]; } else { return false; } } /** * Returns the next step to the current step in the database. * * This will only return something if you've previously visited * the step ahead of the current step, and then gone back a step. * * @return MultiFormStep|boolean|void */ public function getNextStepFromDatabase() { if ($this->SessionID && is_numeric($this->SessionID)) { $nextSteps = $this->config()->get('next_steps'); if (is_string($nextSteps)) { return DataObject::get_one($nextSteps, "\"SessionID\" = {$this->SessionID}"); } elseif (is_array($nextSteps)) { return DataObject::get_one($nextSteps[0], "\"SessionID\" = {$this->SessionID}"); } else { return false; } } } /** * Accessor method for self::$next_steps * * @return string|array */ public function getNextSteps() { return $this->config()->get('next_steps'); } /** * Returns the previous step, if there is one. * * To determine if there is a previous step, we check the database to see if there's * a previous step for this multi form session ID. * * @return string|void Classname of a {@link MultiFormStep} subclass */ public function getPreviousStep() { $steps = MultiFormStep::get()->filter('SessionID', $this->SessionID)->sort('LastEdited', 'DESC'); if ($steps) { foreach ($steps as $step) { $step->setForm($this->form); if ($step->getNextStep()) { if ($step->getNextStep() == static::class) { return get_class($step); } } } } } /** * Retrieves the previous step class record from the database. * * This will only return a record if you've previously been on the step. * * @return MultiFormStep subclass */ public function getPreviousStepFromDatabase() { if ($prevStepClass = $this->getPreviousStep()) { return DataObject::get_one($prevStepClass, "\"SessionID\" = {$this->SessionID}"); } } /** * Get the text to the use on the button to the previous step. * @return string */ public function getPrevText() { return _t(__CLASS__ . '.BACK', 'Back'); } /** * Get the text to use on the button to the next step. * @return string */ public function getNextText() { return _t(__CLASS__ . '.NEXT', 'Next'); } /** * Get the text to use on the button to submit the form. * @return string */ public function getSubmitText() { return _t(__CLASS__ . '.SUBMIT', 'Submit'); } /** * Sets the form that this step is directly related to. * * @param MultiForm $form subclass */ public function setForm($form) { $this->form = $form; } /** * @return Form */ public function getForm() { return $this->form; } // ##################### Utility #################### /** * Determines whether the user is able to go back using the "action_back" * Determines whether the user is able to go back using the "action_back" * Determines whether the user is able to go back using the "action_back" * form action, based on the boolean value of $can_go_back. * * @return boolean */ public function canGoBack() { return $this->config()->get('can_go_back'); } /** * Determines whether this step is the final step in the multi-step process or not, * based on the variable $is_final_step - which must be defined on at least one step. * * @return boolean */ public function isFinalStep() { return $this->config()->get('is_final_step'); } /** * Determines whether the currently viewed step is the current step set in the session. * This assumes you are checking isCurrentStep() against a data record of a MultiFormStep * subclass, otherwise it doesn't work. An example of this is using a singleton instance - it won't * work because there's no data. * * @return boolean */ public function isCurrentStep() { return (static::class == get_class($this->getSession()->CurrentStep())) ? true : false; } /** * Add a CSS-class to the step. If needed, multiple classes can be added by delimiting a string with spaces. * * @param string $class A string containing a classname or several class names delimited by a space. * @return MultiFormStep */ public function addExtraClass($class) { // split at white space $classes = preg_split('/\s+/', $class); foreach ($classes as $class) { // add classes one by one $this->extraClasses[$class] = $class; } return $this; } /** * Remove a CSS-class from the step. Multiple classes names can be passed through as a space delimited string. * * @param string $class * @return MultiFormStep */ public function removeExtraClass($class) { // split at white space $classes = preg_split('/\s+/', $class); foreach ($classes as $class) { // unset one by one unset($this->extraClasses[$class]); } return $this; } /** * @return string */ public function getExtraClasses() { return join(' ', array_keys($this->extraClasses)); } /** * Returns the submitted value, if any, of any steps. * * @param string $fromStep (classname) * @param string $key * * @return mixed */ public function getValueFromOtherStep($fromStep, $key) { // load the steps in the cache, if this one doesn't exist if (!array_key_exists('steps_' . $fromStep, $this->step_data_cache)) { $steps = self::get()->filter('SessionID', $this->form->session->ID); if ($steps) { foreach ($steps as $step) { $this->step_data_cache['steps_' . $step->ClassName] = $step->loadData(); } } } // check both as PHP isn't recursive if (isset($this->step_data_cache['steps_' . $fromStep])) { if (isset($this->step_data_cache['steps_' . $fromStep][$key])) { return $this->step_data_cache['steps_' . $fromStep][$key]; } } return null; } /** * allows to get a value from another step copied over * * @param FieldList $fields * @param string $formStep * @param string $fieldName * @param string $fieldNameTarget (optional) */ public function copyValueFromOtherStep(FieldList $fields, $formStep, $fieldName, $fieldNameTarget = null) { // if a target field isn't defined use the same fieldname if (!$fieldNameTarget) { $fieldNameTarget = $fieldName; } $fields->fieldByName($fieldNameTarget)->setValue($this->getValueFromOtherStep($formStep, $fieldName)); } /** * Gets the linked MultiFormSession * @return MultiFormSession */ public function getSession() { return $this->Session(); } } |