Version: current

      1 <?php
      2 /**
      3  * Расширение для подключения к странице пошаговой формы заказа
      4  * За основу взят аналогичный класс от sshop
      5  * TODO добавить итератор для шагов чтобы можно было выводить их список с указанием текущего положения формат (ID, Title, Link, IsCurrent, IsPast, IsFuture, Pos)
      6  *
      7  * @package cart
      8  * @subpackage steppedcheckout
      9  * @author dvp
     10  */
     11 class SteppedCheckout extends Extension {
     12
     13     static $first_step = null;
     14
     15     /**
     16      * Массив шагов (ассоциативный)
     17      *
     18      * формат: 'название_шага' => 'Класс_шага'
     19      * Класс_шага должен иметь action название_шага - на нее передается управление при переходе на шаг
     20      * @see CheckoutStep
     21      *
     22      * @static
     23      */
     24     static $steps = null;
     25
     26     /**
     27      * Настраивает шаги формы и подключает к CheckoutPage_Controller нужные расширения
     28      */
     29     static function setup($steps = null){
     30         CheckoutPage::$use_steps = true;
     31
     32         // Если уже настроено - выходим. 2 раза настраивать нельзя
     33         if (self::$steps) return;
     34
     35         if (!$steps) {
     36             // Попробуем создать шаги сами
     37             $steps = array();
     38
     39             if (CartSiteConfig::CartRegistraionAvailable()) {
     40                 $steps['membership'] = 'CheckoutStep_Membership';
     41             }
     42
     43             $steps['contactdetails'] = 'CheckoutStep_ContactDetails';
     44
     45             if (Order::$use_shipping) {
     46                 $steps['shippingmethod'] = 'CheckoutStep_ShippingMethod';
     47             }
     48
     49             if (Order::$use_payments) {
     50                 $steps['paymentmethod'] = 'CheckoutStep_PaymentMethod';
     51             }
     52
     53             $steps['summary'] = 'CheckoutStep_Summary';
     54         }
     55
     56         Object::add_extension("CheckoutPage_Controller", "SteppedCheckout");
     57         foreach($steps as $action => $classname){
     58             Object::add_extension("CheckoutPage_Controller", $classname);
     59         }
     60
     61         if (!self::$first_step) {
     62             reset($steps);
     63             self::$first_step = key($steps);
     64         }
     65         self::$steps = $steps;
     66     }
     67
     68     /**
     69      * Redirect back to start of checkout if no cart started
     70      */
     71     function onAfterInit(){
     72         $action = $this->owner->getRequest()->param('Action');
     73         if(!Cart::has_items() && !empty($action) && isset(self::$steps[$action])){
     74             Director::redirect($this->owner->Link());
     75             return;
     76         }
     77     }
     78
     79     /**
     80      * Check if passed action is the same as the current step
     81      */
     82     function IsCurrentStep($name){
     83         if($this->owner->getAction() === $name){
     84             return true;
     85         }
     86         if($this->owner->getAction() == "index" && $this->actionPos($name) === 0){
     87             return true;
     88         }
     89         return false;
     90     }
     91
     92     /**
     93      * Check if passed action is for a step before current
     94      */
     95     function IsPastStep($name){
     96         //echo "ispast ";
     97         return $this->compareActions($name,$this->owner->getAction()) < 0;
     98     }
     99
    100     /**
    101      * Check if passed action is for a step after current
    102      */
    103     function IsFutureStep($name){
    104         //echo "isfuture ";
    105         return $this->compareActions($name,$this->owner->getAction()) > 0;
    106     }
    107
    108     /**
    109      *  Возвращает список шагов процесса оформления заказа со ссылками, параметрами LinkingMode, LinkOrCurrent и Active
    110      *  LinkingMode = filled | current | future
    111      *  LinkOrCurrent = link | current
    112      *  Active = 1 - шаг доступен, 0 - шаг пропускается
    113      *
    114      *  @return DataObjectSet
    115      *
    116      */
    117     function StepsList($activeOnly=false) {
    118         $steps = new DataObjectSet();
    119         $conf = SiteConfig::current_site_config();
    120         foreach(self::$steps as $action => $step) {
    121             if ($activeOnly && !singleton($step)->StepIsActive()) {
    122                 continue;
    123             }
    124             $t = array();
    125             $t['Title'] = _t('CheckoutPage.step_'.$action, 'Step '.$action);
    126             $t['Link'] = $this->owner->Link($action);
    127             $t['Action'] = $action;
    128             $t['LinkingMode'] = ($this->IsCurrentStep($action) ? 'current' : ($this->IsFutureStep($action) ? 'future' : (!singleton($step)->StepIsActive() ? 'skipped' : 'filled')));
    129             $t['LinkOrCurrent'] = ($this->IsCurrentStep($action) ? 'current' : 'link');
    130             $t['Active'] = (int)singleton($step)->StepIsActive();
    131             $steps->push(new ArrayData($t));
    132         }
    133         return $steps;
    134     }
    135
    136     /**
    137      * Get first step from stored steps
    138      */
    139     function index(){
    140         if (self::$first_step && Cart::has_items()){
    141             return $this->owner->redirect($this->owner->Link(self::$first_step));
    142             //return $this->owner->{self::$first_step}();
    143         }
    144         return array();
    145     }
    146
    147     /**
    148      * Check if one step comes before or after the another
    149      */
    150     private function compareActions($action1, $action2){
    151         return $this->actionPos($action1) - $this->actionPos($action2);
    152     }
    153
    154     /**
    155      * Get the numerical position of a step
    156      */
    157     private function actionPos($incoming){
    158         $count = 0;
    159         foreach (self::$steps as $action => $step){
    160             if ($action == $incoming) {
    161                 return $count;
    162             }
    163             $count++;
    164         }
    165         return $count;
    166     }
    167
    168 }
    169
    170
    171 /**
    172  * Добавляем сообщения для шагов
    173  *
    174  * Список возможных полей для шагов формируем по классам-потомкам от CheckoutStep.
    175  * Это не совсем правильно т.к. теоретически один класс может использоваться на 2-х разных шагах,
    176  * но список актуальных шагов на момент генерации БД недоступен.
    177  * При редактировании CheckoutPage видимы только поля тех шагов, которые определены в конфиге.
    178  *
    179  * Чтобы не изменять структуру БД в зависимотси от шагов, возможно хранить сообщения в отдельной таблице
    180  * - тогда можно генерирвать записи в таблице в момент редактирования CheckoutPage по актуальым шагам.
    181  *
    182  */
    183 class SteppedCheckout_PageMessages extends DataObjectDecorator {
    184
    185     function extraStatics() {
    186         $db = array();
    187         foreach (ClassInfo::subclassesFor('CheckoutStep') as $class) {
    188             if ($class == 'CheckoutStep') continue;
    189             $db[self::stepMessageName(singleton($class)->stepName())] = 'HTMLText';
    190         }
    191         return array('db' => $db);
    192     }
    193
    194     function updateCMSFields(&$fields) {
    195         if (!Object::has_extension("CheckoutPage_Controller", "SteppedCheckout")) return;
    196         $fields->addFieldToTab('Root.Content', new Tab("Steps", _t('CheckoutPage.tab_Steps', 'Step Messages')), 'Metadata');
    197         foreach (SteppedCheckout::$steps as $stepClass) {
    198             $step = singleton($stepClass);
    199             $name = $this->stepMessageName($step->stepName());
    200             $fields->addFieldToTab('Root.Content.Steps', new HtmlEditorField($name, $step->stepTitle(), 12));
    201         }
    202     }
    203
    204     /**
    205      * Возвращает сообщение для шага
    206      *
    207      * @param string $step - кодовое название шага
    208      *
    209      * @return string - сообщение из БД
    210      */
    211     function getStepMessage($step) {
    212         $name = $this->stepMessageName($step);
    213         return $this->owner->getField($name);
    214     }
    215
    216     /**
    217      * Формирует имя поля в котором будет храниться сообщение шага
    218      *
    219      * @param string $step - кодовое название шага
    220      *
    221      * @return string - имя поля
    222      */
    223     private static function stepMessageName($step) {
    224         return 'Step' . ucfirst($step);
    225     }
    226
    227 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -