MODX - Shopkeeper3

Есть много расширений для реализации корзины интернет-магазина. Shopkeeper - самый популярный. На мой взгляд. Хотя и не самый простой. Не буду расписывать порядок установки. Литературы полно. Опишу только важные моменты. Опять же с моей точки зрения.

Есть жесткае требование. Если хотите запустит корзину с поддержкой ajax, а это практически всегда так, то нужно обязательно определить наборы параметров.

Пример вызова:    
[[!Shopkeeper3@cart_catalog]]  - cart_catalog - и есть набор параметров.
Важно понимать суть этого действия. А суть такова:
  • У ShK, он же Shopkeeper, есть параметры по умолчанию. Их можно посмотреть в параметрах этого сниппета.
  • Вызывая его с другим набором параметров - мы переопределяем некоторые параметры новыми. Очень удобно реализовано.
  • Т.е. в новом наборе мы указывает только те, которые хотим переопределить.
  • Все это позволяет сделать обновление модуля безопасным. Т.е. в этом случае обновятся параметры по умолчанию, а наши наборы не поменяются. Вообще такое поведение уже стало стандартным. Впервые я с таким столкнулся в extJS.
Обычно устанавливаются 2 набора параметров - для "малой корзины"(для любой страницы) и "большой"(для страницы оформления заказа). Я все шаблоны shopkeeper выношу в особый каталог.
  tplPath => assets/TPL/com/shK/tpl/
  cartRowTpl => @FILE 1_1_cartRow.tpl
  ....................
  ....................
 

  • tplPath - путь к шаблонам, я его сразу меняю на свой. И все шаблоны подключаю как файлы. Это удобнее.
  • style - Стиль корзины (по умолчанию `default`). Для отключения в значении указать «0» (без кавычек); Я отключаю и подключаю свой через Less.
  • cartHelperTpl - Имя чанка для всплывающего блока (хелпера) (по умолчанию генерится прямо в shopkeeper.js). Можно назначить только как чанк.
  • SHK.options.helperHtml - реальный html хелпера. Где SHK - объект JS. То есть, его можно переопределить только через JS.

Это может понадобиться, например, при реализации "покупки в один клик". Когда скрипт php запускается вне modx. Можно напрямую в базу прописать. Если в эво достаточно прописать в одну таблицу, то в REVO их две, что логично:

1.shopkeeper3_orders - заголовки заказов. Кроме фиксированных имеет json-поле "contacts", куда можно добавить произволные поля в формате "name, value, label".
2.shopkeeper3_purchases  - товары в заказе. Также имеет json-поле "data" в которую обычно попадает вся страница, кроме id и контент. Еще туда добавляется url.    
    
Кроме прямой записи, можно использовать классы SHK. Т.е. через xPDO. Пример взят из реализации "покупки в один клик", т.е. одна позиция товара. Алгоритм такой:
  1. Подключаем SHK
    if(!defined('SHOPKEEPER_PATH')){define('SHOPKEEPER_PATH', MODX_CORE_PATH."components/shopkeeper3/");}
    require_once SHOPKEEPER_PATH . "model/shopkeeper.class.php";
    $shopCart = new Shopkeeper( $modx, $properties );    
    $modx->addPackage( 'shopkeeper3', SHOPKEEPER_PATH . 'model/' );
            
  2. Формируем json для поля contacts
    $contacts=array();
    $arrItem=array();
    $arrItem['name']="fullname";
    $arrItem['value']=$hook->fields['name'];
    $arrItem['label']="Имя";                        
    		$contacts[]=$arrItem;
    
    $arrItem['name']="email";
    $arrItem['value']=$hook->fields['email'];
    $arrItem['label']="Адрес эл. почты";
    		$contacts[]=$arrItem;
    .................................        
    $contactsJS = json_encode($contacts);
                
  3. Создаем объект shk_order и сохраняем заголовок заказа.
    $order = $modx->newObject('shk_order');
    $insert_data = array(
            'contacts' => $contactsJS,
            'options' => '',
            'price' => $hook->fields['prod_count']*$hook->fields['prod_price'], 
            'currency' => 'руб.',
            'date' => strftime('%Y-%m-%d %H:%M:%S'),
            'sentdate' => strftime('%Y-%m-%d %H:%M:%S'),            
            'note' => '',
            'email' => $hook->fields['email'],
            'phone' => $hook->fields['phone'],
            'status' => '1'
    );                                                             
    
    $userId = $modx->getLoginUserID( $modx->context->key );
    if( !$userId ) $userId = 0;
    if( $userId ) $insert_data['userid'] = $userId;
    $order->fromArray($insert_data);
    $saved = $order->save();
                
  4. Создаем объект shk_purchases и сохраняем в данном случае одну позицию.
    if($saved ){
        $fields_data=array();
        $fields_data['url']= '/'.$modx->makeUrl($hook->fields['prod_id']);
        $fields_data_str = json_encode( $fields_data);
        $insert_data = array(
            'p_id' => $hook->fields['prod_id'],
            'order_id' => $order->id,
            'name' => $hook->fields['prod_name'],
            'price' => $hook->fields['prod_price'],
            'count' => $hook->fields['prod_count'],
            'class_name' => 'modResource',
            'data' => $fields_data_str
        );
        $purchase = $modx->newObject('shk_purchases');
        $purchase->fromArray( $insert_data );
        $purchase->save();
    }
                
  5. Вызывем события SHK.
    $modx->invokeEvent( 'OnSHKChangeStatus', array( 'order_ids' => array( $order->id ), 'status' => $order->status ) );
    $evtOut = $modx->invokeEvent('OnSHKsaveOrder',array('order_id' => $order->get('id')));
                

Этот раздел касается EVO, но в REVO все почти так же.

Есть одна засада в shk. Это подключение его js скпритов. Они "тупо" встравляются сниппетом прямо в head. К тому же асинхронно. А это чревато. Т.к. при такой загрузке скрипт загружается и выполняется сразу. И может так получится что он загрузиться до загрузки jQuery, со всеми траблами сопутствующими. Это одна проблема. Вторая - чтобы угодить гуглу - надо все скрипты загрузить после загрузки страницы и желательно все скрипты слить в один файл и "ужать". Просмотрев исходник - выяснил порядок загрузки js сниппетом shk:

  • noJavaScript - параметр, позволяющий отключить загрузку js.
  • noConflict - параметр, переводит jQuery в режим совместимости. Лучше всегда ставить в 0, не полагаясь на дефолтную установку. Иначе геморою обретете. На себе проверил. Ну это попутно, к нашей проблеме не отсносится.
  • Генерится блок для вставки в head
      $jsSrc ="
       
        <script src=\"".SHOPKEEPER_URL."js/shopkeeper.js\" type=\"text/javascript\" async ></script>
      ";
      
      $jsSrc.="\n\t<script type=\"text/javascript\">\n\t<!--";
        
        if($shkconf['noConflict']){$jsSrc .="
          jQuery.noConflict();";
        }
        
      $jsSrc .="
          var site_url = '".$site_url."';
          var shkOptions = {
            stuffCont: '".$shkconf['stuffCont']."',
            lang: '".$shkconf['lang']."',
            currency: '".$shkconf['currency']."',
            orderFormPage: '[~".$shkconf['orderFormPage']."~]',
            cartTpl: ['".$shkconf['cartTpl']."','".$shkconf['cartRowTpl']."','".$shkconf['additDataTpl']."'],
            priceTV: '".$shkconf['priceTV']."'";
          if($shkconf['cartType']!='full'){$jsSrc .=",\n\tcartType: '".$shkconf['cartType']."'";}
          if($shkconf['counterField']){$jsSrc .=",\n\tcounterField: true";}
          if($shkconf['changePrice']){$jsSrc .=",\n\tchangePrice: true";}
          if($shkconf['noCounter']){$jsSrc .=",\n\tnoCounter: true";}
          if($shkconf['flyToCart']!='helper'){$jsSrc .=",\n\tflyToCart: '".$shkconf['flyToCart']."'";}
          if(!$shkconf['linkAllow']){$jsSrc .=",\n\tlinkAllow: false";}
          if($shkconf['style']){$jsSrc .=",\n\tstyle:'".$shkconf['style']."'";}
          if($shkconf['noLoader']){$jsSrc .=",\n\tnoLoader: true";}
          if($shkconf['debug']){$jsSrc .=",\n\tdebug: true";}
          
          if($shkconf['cartHelperTpl']){
            $helperChunk = preg_split("/[\r\n]+/", trim($shopCart->fetchTpl($shkconf['cartHelperTpl'])));
            $helperStr = '';
            for($i=0;$iscript src=\"".SHOPKEEPER_URL."lang/".$shkconf['lang'].".js\" type=\"text/javascript\"></script>
      
      
      if($shkconf['debug']){
        $jsSrc .= "
        <script src=\"".SHOPKEEPER_URL."js/log4javascript.js\" type=\"text/javascript\"></script>
        <script src=\"".SHOPKEEPER_URL."js/shkdebug.js\" type=\"text/javascript\"></script>";
      }
  • $modx->regClientStartupScript($jsSrc); - непосредсвенно вставка в head

Т.е. для "автономной" загрузки скриптов js shk нужно установить noJavaScript="1", самим загрузить shopkeeper.js и сделать js c объектом shkOptions. Можно просто скопировать кусок из исходного кода "прежней" страницы:

      var site_url = 'https://mySite.ru/';
      var shkOptions = {
        stuffCont: 'div.shk-item',
        lang: 'russian-UTF8',
        currency: 'руб.',
        orderFormPage: 'oformlenie-zakaza',
        cartTpl: ['cppvShkCartTpl','@FILE:assets/snippets/shopkeeper/chunks/ru/chunk_shopCartRow.tpl',''],
        priceTV: 'price',
	cartType: 'small',
	shkHelper: '<div id="stuffHelper" class="text-center fs16">'
+'<div>'
+'Вы выбрали:  <b id="stuffHelperName"></b>'
+'</div>'
+'<div class="shs-count mrgT10 fb fs14" id="stuffCount">'
+'Кол-во <input type="text" size="2" name="count" value="1" maxlength="3"/>'
+'</div>'
+'<div class="mrgT10">'
+'<button class="shk-but btn-primary btn-sm btn" id="confirmButton">ОК</button>'
+' '
+'<button class="shk-but btn-info btn-sm btn" id="cancelButton">Отмена</button>'
+'</div>'
+'</div>'

	};
Ну тут все понятно. shkOptions - по сути копия конфигурации сниппета shk.

Ну и не забыть вызвать после загрузки всех предыдущих:

jQuery(document).ready(function(){
     jQuery(shkOptions.stuffCont).shopkeeper();
});
Или сразу "раскрыв" переменные:
jQuery(document).ready(function(){
     jQuery("div.shk-item").shopkeeper();
});

После оформления заказа - клиент получает письмо. Поскольку я предпочитаю сам письмо составить, меня интересовало - как собрать его в своем хуке. Плейсхолдеры генерятся в хуке FormIt shk_fihook. Это сниппет.

Само тело заказа с позициями:        
   $orderOutputData = $shopCart->getOrderData( $order->id );
    
В нем вызываются чанк orderDataOuter:
<p><b>Состав заказа</b></p>
        <>table>
                <thead>
                    <tr class="active">
                        <th>Наименование</th>
                        <th>Параметры</th>
                        <th>Кол-во, шт.</th>
                        <th>Цена, [[+currency]]</th>
                    </tr>
                </thead>
            <tbody>
                [[+purchases]]
            </tbody>
            <tfoot>
                <tr class="cart-order">
                    <td colspan="3" style="text-align: right;">
                        [[+delivery]]
                    </td>
                    <td>
                        <b>[[+delivery_price:num_format]]</b>
                    </td>
                </tr>
                <tr class="cart-order">
                    <td colspan="3" style="text-align: right;">
                        <b>Итого:</b>
                    </td>
                    <td>
                        <b>[[+price:num_format]]</b>
                    </td>
                </tr>
            </tfoot>
        </table>

<p><b>Контактные данные</b></p>

<table>
    <colgroup>
        <col width="50%" span="2">
    </colgroup>
    <tbody>
        [[+contacts]]
    </tbody>
</table>
    
Сами строки в чанке orderDataRow:
<tr class="cart-order">
    <td>
        <b>[[+name]]</b>
    </td>
    <td>
        [[+addit_data:default=`—`]]
    </td>
    <td>
        [[+count]] шт.
    </td>
    <td>
        [[+price]] [[+currency]]
    </td>
</tr>
    
В итоге shk_fihook генерит новые свойства для $hook:
                
$hook->setValues(array(
    'orderID' => $order->get('id'),
    'orderDate' => $order->get('date'),
    'orderPrice' => $order->get('price'),
    'orderCurrency' => $shopCart->config['currency'],
    'orderOutputData' => $orderOutputData
    

getOrderData( $order_id ) - метод Shopkeeper. В последней версии ShK все по уму сделано. Почти все через процессоры. Т.е. максимально используются возможности REVO. Процессор getorder находит данные заказа и возвращает массив:
$output = array(
    'success' => true,
    'message' => '',
    'object' => $order_data
);
строки с товарами в $order_data['purchases']=$this->getPurchases( $order_id ); getPurchases( $order_id ) - делает запрос к базе shK.

Тут же запрашиваются шаблоны.

$mailContactsRowTpl = $this->modx->getOption( 'shk3.mail_contacts_row_tpl', null, 'mailContactsRow' );
$orderDataRowTpl = $this->modx->getOption( 'shk3.mail_order_data_row_tpl', null, 'orderDataRow' );
$orderOuterTpl = $this->modx->getOption( 'shk3.mail_order_data_tpl', null, 'orderDataOuter' );
Т.е. эти все шаблоны можно переопределить. Это делается в настройках системы. Почему системных? Потомучто синтаксис такой у getOption( ключ, null, значение_по_умолчанию ). При кастомизации не следует уродовать встроенные чанки, а назначить свои.

В итоге процессор генерит массив для передачи в чанк строк заказа:

    
$output=Array
(
    [0] => Array
        (
            [type] => document
            [contentType] => text/html
            [pagetitle] => "Мед развесной"
            [longtitle] => 
            [description] => "мед собранный на чистых полях Тотемского района"
            [alias] => item531170
            [link_attributes] => 
            [published] => 1
            [pub_date] => 0
            [unpub_date] => 0
            [parent] => 500210
            [isfolder] => 
            [introtext] => 
            [content] => 
            [richtext] => 1
            [template] => 4
            [menuindex] => 0
            [searchable] => 1
            [cacheable] => 1
            [createdby] => 0
            [createdon] => 2018-01-06 23:13:56
            [editedby] => 3
            [editedon] => 2018-02-19 12:00:44
            [deleted] => 
            [deletedon] => 0
            [deletedby] => 0
            [publishedon] => 0
            [publishedby] => 0
            [menutitle] => 
            [donthit] => 
            [privateweb] => 
            [privatemgr] => 
            [content_dispo] => 0
            [hidemenu] => 
            [class_key] => modDocument
            [context_key] => web
            [content_type] => 1
            [uri] => item531170.html
            [uri_override] => 0
            [hide_children_in_tree] => 0
            [show_in_tree] => 1
            [properties] => 
            [url] => https://pchelocom.ru/item531170.html
            [id] => 65
            [p_id] => 531170
            [order_id] => 58
            [name] => "Мед развесной"
            [price] => 90
            [count] => 1
            [options] => 
            [class_name] => modResource
            [package_name] => 
        )

)

Ну и, наконец, парсятся шаблоны процессором renderorderdata:

$response = $this->modx->runProcessor('renderorderdata',
    array(
        'order_data' => $order_data,
        'orderOuterTpl' => $orderOuterTpl,
        'orderContactsTpl' => $mailContactsRowTpl,
        'orderPurchaseRowTpl' => $orderDataRowTpl
    ),
    array('processors_path' => $this->modx->getOption('core_path') . 'components/shopkeeper3/processors/web/')
);
У меня была надежда, что можно будет подключить "внешние" шаблоны. Как сейчас все продвинутые сниппеты делают. Ан нет. ShK оказался в этом смысле консервативен. И эти шаблоны можно назначить только как чанки. Поскольку они невелики по размеру, то это некритично.


Комментарии 0






Разрешённые теги: <b><i><br>Добавить новый комментарий: