- <?php 
- /* 
-  * Copyright (C) 2017 Karmabunny Pty Ltd. 
-  * 
-  * This file is a part of SproutCMS. 
-  * 
-  * SproutCMS is free software: you can redistribute it and/or modify it under the terms 
-  * of the GNU General Public License as published by the Free Software Foundation, either 
-  * version 2 of the License, or (at your option) any later version. 
-  * 
-  * For more information, visit <http://getsproutcms.com>. 
-  */ 
-   
- namespace Sprout\Controllers; 
-   
- use Exception; 
-   
- use Kohana_404_Exception; 
-   
- use karmabunny\pdb\Exceptions\QueryException; 
- use Sprout\Helpers\Notification; 
- use Sprout\Helpers\Pdb; 
- use Sprout\Helpers\Session; 
- use Sprout\Helpers\Url; 
- use Sprout\Helpers\Validator; 
- use Sprout\Helpers\View; 
- use Sprout\Helpers\Sprout; 
-   
-   
- /** 
- * Supports multistep forms 
- */ 
- abstract class MultiStepFormController extends Controller { 
-     /** 
-      * Steps and functions/views used to implement them 
-      * Keys are step numbers, values are function (and matching view) names 
-      * e.g. [1 => 'intro', 2 => 'personal_details'] 
-      */ 
-     protected $steps = []; 
-   
-     /** Key for partitioning session data associated with this form, i.e. $_SESSION[$this->session_key] */ 
-     protected $session_key = 'DUMMY'; 
-   
-     /** Route to form, relative to site root */ 
-     protected $route = 'multistep'; 
-   
-     /** Page title to be displayed on form steps */ 
-     protected $page_title = 'Form'; 
-   
-     /** Appendage to route, where user is redirected after save on final step */ 
-     protected $complete_function = 'complete'; 
-   
-     /** Directory in which views are stored, without trailing slash */ 
-     protected $view_dir = ''; 
-   
-     /** Table into which data will be saved at the final step */ 
-     protected $table = ''; 
-   
-   
-     /** 
-     * The method used to drive the forms at each step 
-     * This should be called by the entry function of the subclass controller 
-     * @param int $step The current step 
-     * @return View The view containing the form for that step 
-     */ 
-     protected function form($step = -1) 
-     { 
-         $step = (int) $step; 
-   
-         // Allow arrays with 0-based or 1-based ordering 
-         $first_step = $this->firstStep(); 
-         if ($step < $first_step) $step = $first_step; 
-   
-         $session = $this->getSession(); 
-         $this->checkStep($session, $step); 
-   
-         $data = @$session['data']; 
-   
-         $view_name = $this->steps[$step]; 
-         if (!$view_name) { 
-             throw new Kohana_404_Exception(); 
-         } 
-   
-         $view = new View("{$this->view_dir}/{$view_name}"); 
-         $view->submit_url = "{$this->route}/submit/{$step}"; 
-         $view->session_key = $this->session_key; 
-         $view->step = $step; 
-         $view->steps = count($this->steps); 
-         $view->data = $data; 
-         if (!empty($session['field_errors'])) { 
-             $view->errors = $session['field_errors']; 
-         } else { 
-             $view->errors = []; 
-         } 
-   
-         // Allow loading custom data (e.g. for select fields) 
-             $this->$view_name($view); 
-         } 
-   
-         return $view; 
-     } 
-   
-   
-     /** 
-      * Display the thanks message after the process has been completed 
-      * @return void 
-      */ 
-     public function complete() 
-     { 
-         $view = new View("{$this->view_dir}/complete"); 
-   
-         $page_view = new View('skin/inner'); 
-         $page_view->main_content = $view->render(); 
-         $page_view->page_title = "{$this->page_title}: complete"; 
-         $page_view->controller_name = $this->getCssClassName(); 
-         echo $page_view->render(); 
-     } 
-   
-   
-     /** 
-     * The method used to drive the form submissions at each step 
-     * This should be called by the submit function of the subclass controller 
-     * @param int $step The current step 
-     * @return void On the final step, the save() method is called. On earlier 
-     *         steps, the user is redirected to the form for the next step. 
-     */ 
-     protected function submit($step) 
-     { 
-         $step = (int) $step; 
-   
-         $session = &self::getSession(); 
-         $this->checkStep($session, $step); 
-   
-         $handler_function = $this->steps[$step]; 
-         if (!$handler_function) throw new Kohana_404_Exception(); 
-         $handler_function .= 'Submit'; 
-   
-         if (!isset($session['data'])) $session['data'] = array(); 
-   
-             throw new Exception("Missing handler $handler_function"); 
-         } 
-   
-         $this->$handler_function($step); 
-   
-         // Allow arrays with 0-based or 1-based ordering 
-         $last_step = end($step_nums); 
-         if ($step == $last_step) { 
-             $this->save(); 
-         } else { 
-             ++$step; 
-             $session['step'] =  $step; 
-             Url::redirect($this->buildUrl($step)); 
-         } 
-     } 
-   
-   
-     /** 
-      * Gets the number (should be 0 or 1) of first step of the process 
-      * @return int Usually 0 or 1, although in theory step 1000 can be the first step if so desired 
-      */ 
-     protected function firstStep() 
-     { 
-         return Sprout::iterableFirstKey($this->steps); 
-     } 
-   
-   
-     /** 
-     * Check the user has access to a step 
-     * @return void A redirect is performed if the user isn't ready for the step 
-     */ 
-     protected function checkStep($session, $step) 
-     { 
-         $session_step = (int) @$session['step']; 
-         if ($session_step < 1) $session_step = 1; 
-   
-         if ($session_step >= $step) return; 
-   
-         Url::redirect($this->buildUrl($session_step)); 
-     } 
-   
-   
-     /** 
-     * Instantiate the session and make sure the session key is useable 
-     * @return array 
-     */ 
-     protected function &getSession() { 
-         $session = Session::instance(); 
-         if (!isset($_SESSION[$this->session_key])) { 
-             $_SESSION[$this->session_key] = array(); 
-         } 
-         return $_SESSION[$this->session_key]; 
-     } 
-   
-   
-     /** 
-      * @param int $step 
-      * @param array $reqd Names of required fields, e.g. ['name', 'email'] 
-      * @param array $rules Each element is an array which is passed to {@see Validator::check}, 
-      *        e.g. [['first_name', 'Validity::length', 0, 20], ['last_name', 'Validity::length', 0, 20]] 
-      * @param mixed $valid a {@see Validator}, or null to create one on the fly. Specifying a Validator 
-      *        allows the addition of rules/errors before this method is called. 
-      * @return void A redirect will occur if validation fails 
-      */ 
-     protected function-  validate ($step, array $reqd, array $rules,-  Validator  $valid = null)
 
-     { 
-         $session = &self::getSession(); 
-   
-         if (!$valid) { 
-             $valid = new Validator($_POST); 
-         } 
-   
-         $valid->required($reqd); 
-   
-         foreach ($rules as $rule) { 
-             $name = Sprout::iterableFirstValue($rule); 
-             $session['data'][$name] = @$_POST[$name]; 
-         } 
-   
-         if ($valid->hasErrors()) { 
-             $session['field_errors'] = $valid->getFieldErrors(); 
-             $valid->createNotifications(); 
-             Url::redirect($this->buildUrl($step)); 
-         } 
-         unset($session['field_errors']); 
-     } 
-   
-   
-     /** 
-     * Builds a URL to the form at a particular step 
-     * @param int $step 
-     * @param bool $include_first Include /0 or /1 ending for the first step 
-     * @return string the URL 
-     */ 
-     protected function buildUrl($step, $include_first = false) 
-     { 
-         $url = $this->route; 
-         if ($step != $this->firstStep() or $include_first) { 
-             $url .= "/{$step}"; 
-         } 
-         return $url; 
-     } 
-   
-   
-     /** 
-      * Do any final preparation / data massaging; then call saveData and finally redirect. 
-      * This should generally be overridden. 
-      */ 
-     protected function save() 
-     { 
-         try { 
-             $id = $this->saveData($this->table); 
-   
-             // If copy-pasting this function, add post-insert stuff like emailing a thank you notice here 
-   
-             Url::redirect("{$this->route}/{$this->complete_function}"); 
-   
-         } catch (QueryException $ex) { 
-             Notification::error('A database error occurred. Please contact us to resolve the issue.'); 
-   
-             // Return to last step of the form 
-             Url ::redirect($this->build_url(end($keys)));
-         } 
-     } 
-   
-   
-     /** 
-     * Save the data, e.g. into a database, after successful validation at the 
-     * final step 
-     * @param string $table The table to save the data in, e.g. 'members' 
-     * @param array $sub_tables The subtables which should be used. The keys 
-     *        must match those used in the $session['data'] array. Each of the 
-     *        values is itself an array with one element. The 0-th value is the 
-     *        subtable to save the data in, and the 1st value is the column in 
-     *        that table which links to the core table, 
-     *        e.g. ['preferences' => ['member_prefs', 'member_id']] 
-     * @param array $extra_fields Extra field values which should be set before 
-     *        performing the inserts. The keys are the table names, and each 
-     *        value is an array of column name to value mappings, 
-     *        e.g. ['members' => ['date_added' => Pdb::now(), ...]] 
-     * @return int|null The insert id from the newly created record, 
-     *         or null if no table is specified 
-     */ 
-         $session = &self::getSession(); 
-         $data = $session['data']; 
-   
-         if (- IN_PRODUCTION ) unset($_SESSION[$this->session_key]);
 
-   
-         // Nothing can be saved in DB if table isn't specified 
-         if ($table == '') return null; 
-   
-         // Move data for subtables out of the data for the core table 
-         foreach ($sub_tables as $key => $config) { 
-             $sub_data[$key] = array(); 
-   
-             // Transpose data, e.g. ['name'][1-5] => [1-5]['name'] 
-             $values = $data[$key]; 
-             foreach ($values as $field => $arr) { 
-                 foreach ($arr as $num => $value) { 
-                     $sub_data[$key][$num][$field] = $value; 
-                 } 
-             } 
-         } 
-   
-         // Insert data into core table 
-         foreach ($data as $field => $val) { 
-             $insert_data[$field] = $val; 
-         } 
-         if (isset($extra_fields[$table])) { 
-             foreach ($extra_fields[$table] as $field => $val) { 
-                 $insert_data[$field] = $val; 
-             } 
-         } 
-         $insert_data['date_added'] = Pdb::now(); 
-         $insert_data['date_modified'] = Pdb::now(); 
-   
-         Pdb::transact(); 
-         $id = Pdb::insert($table, $insert_data); 
-   
-         // Insert data into subtables 
-         foreach ($sub_tables as $key => $config) { 
-             list($sub_name, $link_col) = $config; 
-   
-             $insert_data = array($link_col => $id); 
-             foreach ($sub_data[$key] as $record) { 
-                 foreach ($record as $field => $val) { 
-                     $insert_data[$field] = $val; 
-                 } 
-                 if (isset($extra_fields[$sub_name])) { 
-                     foreach ($extra_fields[$sub_name] as $field => $val) { 
-                         $insert_data[$field] = $val; 
-                     } 
-                 } 
-                 Pdb::insert($sub_name, $insert_data); 
-             } 
-         } 
-         Pdb::commit(); 
-   
-         return $id; 
-     } 
- } 
-