Простая корзина товаров без модуля магазин

Недавно хорошие знакомые попросили пристроить им на сайт простую корзину заказов. Как правило, я такими вещами не занимаюсь - для коммерции есть модуль "Магазин".  Но здесь ситуация иная, поэтому по-быстрому на коленке соорудил подобие корзины товаров.

Для упрощения задачи не будем создавать модуль. Надо готовить картинки, типы данных создавать, думать что в какую папку... Пойдем проще.  Делаем все кастомами к модулю webforms. Можно и к любому другому бы, но , во-первых, изначально я предполагал отправлять письма с заказом через этот модуль, чтобы оставались логи в системе; во-вторых, модуль webforms у меня самый свободный - только однажды за всю жизнь пришлось что-то туда дописывать.

Как это работает.

К товару добавляется ссылка "положить в корзину". По клику на нее добавляется единица товара. Набрав товаров можно перейти в корзину, изменить количество. Рассчитывается суммарная стоимость. Далее переход к оформлению заказа, где спрашивается минимальная контактная информация, без регистрации. Заполненная форма отправляется на почту администратору. Корзина сессионная, при закрытии окна браузера очищается.

ВНИМАНИЕ! Я рассчитывал только на tpl-шаблонизатор! Для xslt вам необходимо исправить все методы, осуществляющие вывод данных.

Для начала готовим класс корзины basket. Помещаем его прямо в __custom.php, после основного класса (ведь php позволяет объявлять несколько классов в одном файле), чтобы не заморачиваться с require. Эти методы не надо указывать в permissions.custom.php

    /**
     Простая корзина без наворотов
     1) все данные до оформления заказа хранятся в сессии для всех  пользователей. 
     2) Храним id товара и количество.
     3) Все расчеты в рублях
     4) поскольку сессия и так привязана к пользователю, за user_id  не следим
     5) $_SESSOIN['basket'] = array($id => $count);
     6) Никаких скидок и карт!
 */
     class basket extends singleton {
 
         public static function getInstance()  {
             return parent::getInstance(__CLASS__);
             
         }
 
         protected function __construct() {
             if(isset($_SESSION['basket'])){
                  $this->basket = $_SESSION['basket'];
             }
             else {
                  $this->basket = $_SESSION['basket'] = array();
             }
         }
         
         public function getBasket(){
             return $this->basket;
         }
         
         public function addItem($id){
             if(array_key_exists($id,  $_SESSION['basket'])){
                  $_SESSION['basket'][$id] = $_SESSION['basket'][$id] + 1;
             }
             else{
                  $this->basket[$id] = 1;
                  $_SESSION['basket'][$id] = 1;
             }
             
         }
         
         public function removeItem($id){
             if(array_key_exists($id,  $_SESSION['basket'])){
                  unset($_SESSION['basket'][$id]);
             }
             else{
                  return false;
             }
         }
         
         //Array ( [count] => 5 )
         public function changeBasketItem($id,  $arrItemInfo){
             if(array_key_exists($id,  $_SESSION['basket'])){
                  if(isset($arrItemInfo['count'])){
                      $count = (int)$arrItemInfo['count'];
                      if($count > 0){
                          $_SESSION['basket'][$id] = $count;
                      }
                      else{
                          unset($_SESSION['basket'][$id]);
                      }
                  }
             }
             
 
         }
         
         public function emptyBasket(){
             unset($_SESSION['basket']);
         }
         
         public function checkIsEmpty(){
             if(count($_SESSION['basket'])  > 0) return true;
             else return false;
         }
         
         public function renderBasket($sTemplate){
             
             if(!$sTemplate)  $sTemplate = "default";
             $h = umiHierarchy::getInstance();
             
             //$basket = basket::getInstance();
 
             list($sBasket,  $sEmptyBasket, $sBasketItem) = def_module::loadTemplates("tpls/webforms/basket/{$sTemplate}.tpl",  "basket", "basket_empty", "basket_item");
 
             $block_array  = $block_arr = array();
             
             if($this -> checkIsEmpty()){
 
                  $s = '';
                  $total = 0;
                  foreach($this->getBasket() as $id=>$count){
                     
                      if(!$el = $h -> getElement($id)) continue;
                      if(!$price = $el -> getValue('price')) continue;
                      $block_array['id'] = $id;
                      $block_array['count'] = $count;
                      $block_array['name'] = $el -> getName();
                      $block_array['price'] = $price;
                      $block_array['price_total'] = $price * $count;
                     
                      $total += $block_array['price_total'];
                     
                      $s .= def_module::parseTemplate($sBasketItem, $block_array);
                  }
                  $block_arr['items'] = $s;
                  $block_arr['order_price'] = $total;
                  return def_module::parseTemplate($sBasket, $block_arr);
             }
             else {
                 
                  return def_module::parseTemplate($sEmptyBasket, $block_array);
             }
             
             
         }
         
         
         public function renderOrder($sTemplate){
             
             if(!$sTemplate)  $sTemplate = "default";
             $h = umiHierarchy::getInstance();
             
             //$basket = basket::getInstance();
 
             list($sBasket,  $sEmptyBasket) = def_module::loadTemplates("tpls/webforms/basket/{$sTemplate}.tpl",  "order", "order_empty");
 
             $block_arr =  array();
             
             if($this -> checkIsEmpty()){
 
                  $block_arr['ordered_items'] = $this->renderOrderItems();
                 
                  //данные юзера
                  $block_arr['comments'] = '';
                  $block_arr['phone'] = '';
                  $block_arr['email'] = '';
                  $block_arr['fio'] = '';
                 
                  return def_module::parseTemplate($sBasket, $block_arr);
             }
             else {
                 
                  return def_module::parseTemplate($sEmptyBasket, $block_array);
             }
             
             
         }
         
         public function renderOrderItems(){
                         
             if(!$sTemplate)  $sTemplate = "default";
             $h = umiHierarchy::getInstance();
             
             //$basket = basket::getInstance();
 
             list($sBasketItem,  $items_block) = def_module::loadTemplates("tpls/webforms/basket/{$sTemplate}.tpl",  "order_item",'items_block');
 
             $block_array  = $block_arr = array();
             
             if($this -> checkIsEmpty()){
 
                  $s = '';
                  $total = 0;
                  foreach($this->getBasket() as $id=>$count){
                     
                      if(!$el = $h -> getElement($id)) continue;
                      if(!$price = $el -> getValue('price')) continue;
                      $block_array['id'] = $id;
                      $block_array['count'] = $count;
                      $block_array['name'] = $el -> getName();
                      $block_array['price'] = $price;
                      $block_array['price_total'] = $price * $count;
                     
                      $total += $block_array['price_total'];
                     
                      $s .= def_module::parseTemplate($sBasketItem, $block_array);
                  }
                  $block_arr['items'] = $s;
                  $block_arr['order_price'] = $total;
                 
                  return def_module::parseTemplate($items_block, $block_arr);
             }
             else {
                 
                  return def_module::parseTemplate($sEmptyBasket, array());
             }
         }
     } 

Далее пишем кастомные методы для модуля webforms, к которым и будет прямое обращение из шаблонов.

В кастом записываем методы работы с корзиной:

 public function basket($sTemplate = "default")  {
  return basket::getInstance()->renderBasket($sTemplate);
 }
 public function basket_recalc() {
 //пересчитать корзину
if (isset($_REQUEST['basket_items']))  {
   $arrBasketItems = $_REQUEST['basket_items'];
   foreach ($arrBasketItems as $iElementId => $arrItemInfo) {
    if (isset($arrItemInfo['remove'])) {
    basket::getInstance()->removeItem($iElementId);
    } else {
    basket::getInstance()->changeBasketItem($iElementId, $arrItemInfo);
    }
   }
  }
  $this->redirect(getServer('HTTP_REFERER'));
 }
 
 //ссылка положить единицу товара в корзину
 public function addtobasket($iElementId,  $sTemplate = "default") {
  if (!$sTemplate)  $sTemplate = "default";
  list($sAddToCart)  = $this->loadTemplates("tpls/webforms/basket/{$sTemplate}.tpl", "addtobasket");
  $block_arr =  array();
  $block_arr['element_id']  = $iElementId;
  $block_arr['link']  = $link_add = $this->pre_lang . "/webforms/addtobasket_do/{$iElementId}/";
  return def_module::parseTemplate($sAddToCart,  $block_arr);
 }
 
 //положить единицу товара в корзину
 public function addtobasket_do() {
  $id = isset($_REQUEST['param0'])?  (int) $_REQUEST['param0'] : false;
  if ($id) {
   basket::getInstance()->addItem($id);
  }
  $this->redirect(getServer('HTTP_REFERER'));
 }
 
 /**
  Выбор действия
 */
 public function basket_change() {
  if(isset($_POST['order'])){
   $this->redirect('/webforms/order/');
  }elseif(isset($_POST['recalc'])){
   return $this -> basket_recalc();
  }
  $this->redirect(getServer('HTTP_REFERER'));
 }
 
 
 /**
  Работа с заказами
 */
 //оформляем заказ
 public function order($sTemplate = "default")  {
  $sTpl = getRequest("param0");
  $sFromPage =  getRequest("from_page");
 
  if (is_string($sTpl))  {
   $sTemplate = $sTpl;
  } elseif (!$sTemplate)  {
   $sTemplate = "default";
  }
  $s = basket::getInstance()->renderOrder($sTemplate);
  return $s;
 }
 
 

Метод отправки заказа на почту.

Как говорил, изначально предполагалось использовать методы webforms для отправки сообщений, т.о. можно было бы в интерфейсе модуля просматривать заказы. Увы, оказалось, что ЮМИ начисто вырезает html-форматирование и быстрого решения я не нашел. Пока пришлось сделать заглушку и отправлять письма вручную.

public function order_do(){
 
 $email = getRequest('email');
 $fio = getRequest('fio');
 $phone = getRequest('phone');
 $comments = getRequest('comments');
 
 $items = basket::getInstance()->renderOrderItems();
 
 $message = <<<END
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "//www.w3.org/TR/html4/loose.dtd">
 <html>
 <head>
 <title>Новый заказ на сайте</title>
 </head>
 <body>
  <h2>Новый заказ на сайте</h2>
 ФИО {$fio}
 email {$email}
 Телефон {$phone}
 
 {$items}
 
 <p><b>Комментарии к заказу</b></p>
 {$comments}
 </body>
 </html>
 END;
 
 $oMail = new umiMail();
 
 $oMail->addRecipient('почта-первого-менеджера@bla-bla.ru');
 $oMail->addRecipient('почта-второго-менеджера@bla-bla.ru');
 
 $oMail->setFrom('no-reply@bla-bla.ru','Заказ в магазине');
 $oMail->setSubject('Заказ в магазине');
 $oMail->setContent($message);
 $oMail->commit();
 $oMail->send();
 
 basket::getInstance() -> emptyBasket();
 $this->redirect('/webforms/order/');
 }
 
 };
 

Осталось заполнить шаблоны для корзины и представления товаров в письме

ВНИМАНИЕ! Я предполагал использование только tpl-шаблонов.

В папке tpls/webforms/ создать папку basket
В нее помещаем шаблон корзины и блока заказов

<?php
 
 $FORMS = array();
 
 
 $FORMS['basket'] = <<<END
     <form action="/webforms/basket_change/" method="post" name="basket_frm">
     <h2>Товары в корзине</h2>
         <table border="1" cellspacing="2" width="100%" rules="rows">
             <tr style="background-color: #CCCCCC">
                 <td><b>ID</b></td>
                 <td><b>Наименование</b></td>
                 <td><b>Цена</b></td>
                 <td><b>Количество</b></td>
                 <td><b>Итого</b></td>
                 <td><b>Удалить</b></td>
             </tr>
             %items%
         </table>
         <b>Всего к оплате: %order_price%</b>
         <br />
         <br />
 
         <input type="submit" class="button button_gray" value="Пересчитать корзину" name="recalc" />
         <input type="submit" class="button button_red" value="Оформить заказ" name="order" />
         <input type="hidden" name="from_page" value="%pre_lang%/webforms/basket" />
     </form>
 


     
 END;
 
 $FORMS['basket_empty'] = <<<END
 
     <p>Корзина пуста</p>
 
 
 END;
 
 $FORMS['basket_item'] = <<<END
 
         <tr>
             <td>%id%</td>
             <td>%name%</td>
             <td>%price% р.</td>
             <td><input type="text" name="basket_items[%id%][count]" value="%count%" maxlength="3" style="width:30px; border:1px sold #CCCCCC;" /></td>
             <td>%price_total%</td>
             <td><input type="checkbox" name="basket_items[%id%][remove]" value="1" style="width: 15px; height: 15px;" /></td>
         </tr>
 
 END;
 
 $FORMS['addtobasket'] = <<<END
 <a class="a_button button_red" style="width: 60px" href="%link%">Заказать</a>
 
 END;
 
 
 
 
 
 $FORMS['order'] = <<<END
     <form action="/webforms/order_do/" method="post" name="basket_frm">
     <h2>Оформление заказа</h2>
         %ordered_items%        
         <br />
         <br />
         <p>Укажите на выбор удобную вам контактную информацию, чтобы наши менеджеры могли связаться с вами</p>
         <table>
         <tr><td>Ваше имя</td><td><input type="text" name="fio"></td></tr>
         <tr><td>Контактный e-mail</td><td><input type="text" name="email"></td></tr>
         <tr><td>Телефон</td><td><input type="text" name="phone"></td></tr>
         </table>
         <textarea name="comments"></textarea>
         <input type="submit" class="button button_red" value="Оформить заказ" name="order" />
         <input type="hidden" name="from_page" value="%pre_lang%/webforms/basket" />
     </form>
 


     
 END;
 
 
 $FORMS['items_block'] = <<<END
 
         <table border="1" cellspacing="2" width="100%" rules="rows">
             <tr style="background-color: #CCCCCC">
                 <td><b>ID</b></td>
                 <td><b>Наименование</b></td>
                 <td><b>Цена</b></td>
                 <td><b>Количество</b></td>
                 <td><b>Итого</b></td>
             </tr>
             %items%
         </table>
         <b>Всего к оплате: %order_price%</b>
 
 END;
 
 
 $FORMS['order_empty'] = <<<END
 
     <p>Ваш заказ отправлен. Менеджер свяжется с вами в ближайшее время</p>
 
     
 END;
 
 $FORMS['order_item'] = <<<END
 
         <tr>
             <td>%id%</td>
             <td>%name%</td>
             <td>%price% р.</td>
             <td>%count%</td>
             <td>%price_total%</td>
         </tr>
 
 END;
 
 
 
 ?>

Собираем все вместе.

Поскольку в моем случае использовалась линейка PRO, в ней имеется модуль фотогалереи. Его и было решено использовать в качестве каталога.

К типу данных "Фотография" добавить поле "Цена", с именем "price" и типа "Цена".

В шаблоне вывода товара добавить вывод цены и ссылки на добавление в корзину

<p>Цена %price%P </p><br> <p>%webforms addtobasket(%id%)%</p>

При необходимости вывести краткую форму корзины создать соотв. шаблон. Переход на страницу корзины /webforms/basket/

Конечно, не забыть в permissons.custom.php перечислить все вновь добавленные методы