Корзина магазина без Ubercart только на Флагах и Вьюсах (многа букв и картинки!)

Аватар пользователя Ne_L Ne_L 23 июля 2012 в 18:41

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

Про Ubercart я конечно же в курсе, но использовать его не хочется ввиду его монстрообразности, конечно это отличный модуль, потрясающая функциональность, но пришлось бы добавлять к каждому товару несколько полей, и настраивать их, а у меня в принципе всё уже было, наименование, артикул, цена.
Мне хотелось простого лаконичного решения, с выводом корзины через Views и последующей отправки заказа по эл. почте. Без хранения его в БД, изменения статусов, и прочих заморочек.
Всевозможные модули простых корзин как то Node Basket и Simple Cart от уважаемых Dalay и VladSavitsky подходили в принципе, но не хватало гибкости настроек.

Поэтому решил делать своё решение с помощью темизиации шаблона Views и замечательного модуля Flag, к тому же Views у меня и так использовался для отображения прайсовых таблиц выбранной категории.
Конечной целью, сделать так чтобы рядом с кратким описанием ноды (читай товара) в табличном представлении, появилась кнопка "заказать" которую можно было бы нажимать, проглядывая представления, а потом в таблице корзины, вбив количества и контактные данные, отправить заказ. Типичный интернет магазин.

Итак попробую пошагово описать возможность данного решения:

Первым делом устанавливаю модули: Flag(я использовал версию 6.x-2.0-beta6) и Views(6.x-2.16), также я буду использовать модуль Views Custom Field (он позволит использовать PHP код в поле представления).

Добавляю возможность помечать интересующие товары флагами для заказа:
Создаю флаг для нод того типа данных, который используется для хранения информации о заказываемом товаре, выбираю роли пользователей которым будет разрешено помечать флагами товары.
(С ролями есть нюанс: по умолчанию возможность ставить флаги есть только у авторизированных пользователей, если вы хотите позволить это делать анонимам то вам понадобиться модуль Session API у меня 6.x-1.4).
Мои настройки можно посмотреть на рисунке.

Далее в представлении которое отображает ваши товары нужно добавить связь (Relationships) там в выподающем списке - Flags: Node flag

Затем добавить поле Flags: Flag link

После всего этого у нас в нашем представлении появляется дополнительно к полям ноды ещё и ссылка с возможностью отметить флагом нужные товары.

Дальше создаю представление отображающее корзину с отмеченными товарами. (Проще скопировать вид отображающий товары - т.к. они очень похожи) В этом представлении убираю лишние аргументы, поля.
Создаю поле Customfield: PHP code которое будет выводить окошко для ввода необходимого количества (не пишу "поле ввода" чтобы не путаться с "полями" представления), а также поле подсчитывающее общую стоимость по каждой позиции (актуально т.к. у меня есть поле с ценой). Пока не вбивайте в эти поля ничего - заполним их потом и кровью.

На данном этапе, кстати, я столкнулся со сложностями, когда хотел это всё сделать через Forms API - проблема в том, что если в каждой строке, например таблицы, создавать поля через FAPI они будут принадлежать к отдельным формам и отправляться по отдельности, мне же хотелось чтобы кнопка отправки формы обрабатывала все поля выведенные в представлении.
Поэтому форму задаём в шаблоне "корзинного" представления:

Перехожу в Theme: Information данного представления и выбирю шаблон Style output сохраняю его в папке темы с именем подсказанным в окне интерфейса, вот в этом шаблоне и будет практически весь механизм работы корзины, прикрепил к статье, но привожу листинг ниже:


  if ($_POST['ofor']) {                                                                                  //Если была нажата кнопка "Отправить"
    if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) || $_POST['phone'] || $_POST['info']) { //проверяем наличие хотя бы одного поля с контактами
      $flag = flag_get_flag('cart') or die('Не задан "cart" флаг');           // делаем определение флага
      $formdata = $rows;                                                      // готовим содержание письма для отправки
      $i = 0;                                                                 // х.з. как подругому
      foreach ($_POST as $key => $value) {                                    // пробегаемся по массиву имён переменных с количествами
        if (preg_match('/kolvo.*/', $key)) {                                  // названия которых состоят из kolvo.и.nid
          $flag -> flag('unflag', (substr ($key,8)));                         // чистим флаги
          $formdata[$i]['kolvo'] = $value;                                    // собираем массив для отправки почтой
        }
        elseif (preg_match('/art.*/', $key)) {
          $formdata[$i++]['art'] = $value;                                    // собираем..
        }
      }
      foreach ($formdata as $count) {                                         // создаём часть текста письма с заказом
      unset ($count['ops'],$count['phpcode']);                                // очищаем ненужные переменные чтоб не засорять
      $body .= drupal_html_to_text(implode(' | ',$count)).'
'
;      // конвертируем это всё в текст т.к. если посылать темизированную таблицу то она почему то вставляется в ексель ввиде одного столбца с текстом из ячеек таблицы, как побороть х.з.
      }
      drupal_mail_send (array (                                               // посылаем письмо с заказом
      'id' => 'cart',
      'to' => 'vasya@pupkin.ru>',
      'subject' => 'из корзины',
      'body' => "".$body."E-mail:".$_POST['email']."
"
."Телефон:".$_POST['phone']."
"
."Прочее:".$_POST['info']."
"
."",
      'headers' => array(
                          'MIME-Version' => '1.0',
                          'Content-Type' => 'text/html; charset=UTF-8; format=flowed; delsp=yes',
                          'Content-Transfer-Encoding' => '8Bit',
                          'X-Mailer' => 'Drupal'),
      ));
    unset ($_SESSION['korzina'], $i, $formdata);                             // грохаем переменные корзины в сессии
    $msg = 'Ваш заказ отправлен - наш менеджер обязательно ответит вам';     //задаём ОК-сообщение
    }
    else {                                                //если же какие введённые данные не верны или отсутствуют
      $err_msg = 'Необходимы любые контактные данные';    //задаём НЕОК-сообщение
      unset ($_POST['ofor']);                             //делаем вид что нажали не кнопку "Оформить"
      $_POST['otpr'] = 'ispravte';                        //а кнопку "Отправить"
    }
  }
  elseif ($_POST['pereschitat'] || $_POST['otpr']) {    //Если была нажата кнопка "Пересчитать или Оформить"
    foreach ($_POST as $key => $value) {                //пробегаемся по массиву отправленных данных
      if (preg_match('/kolvo.*/', $key)) {              //и проверяем переменные начинающиеся с "kolvo" содержащие количество товара
        if (!is_numeric($value)) {                      //если там введено что то отличное от количества - задаём НЕОК-сообщение
          $err_msg = 'Введено некорректное количество товара - должны быть целые числа';
          unset ($_POST['otpr']);                       //и делаем вид что нажали не кнопку "Отправить"
          $_POST['pereschitat'] = 'retype';             //а кнопку "Пересчитать"
        }
      };
    }
    if (empty($err_msg)) $_SESSION['korzina'] = $_POST; //если ошибки при проверке количеств не было, то записываем POST в сессию для того чтобы, в случае ухода со страницы, количества не сбросились.
  }
  if (!empty($err_msg)) print '

'

.$err_msg.'';
  if (!empty($msg)) print '

'

.$msg.'';
?>

                                             //Добавляем форму с контактными данными
  if ($_POST['otpr']) {                            //в случае если нажата кнопка "Оформить" и никаких ошибок с количествами нет.
    print "
";
    print theme ('textfield', array(
      '#description' =>'Для электронного ответа на вашу заявку',
      '#name' => 'email',
      '#title' => 'E-mail',
      '#id' => 'email',
      '#size' => 30,
      '#attributes' => array ('placeholder' => 'Ваш e-mail',)));
    print theme ('textfield', array(
      '#description' =>'Для связи',
      '#name' => 'phone',
      '#title' => 'Телефон',
      '#id' => 'phone',
      '#size' => 30,
      '#attributes' => array ('placeholder' => 'Ваш телефон',)));
    print theme ('textarea', array(
      '#description' =>'а десь можно указать дополнительную информацию, ваше имя, реквизиты фирмы, адрес доставки, всё что поможет нам (наиболее быстро) обработать вашу заявку, всю контактную информацию можно вводить сюда',
      '#name' => 'info',
      '#title' => 'Дополнительная информация',
      '#id' => 'info',
      '#rows' => 4,
      '#attributes' => array ('placeholder' => 'Дополнительно', 'maxlength' => 500,)));
    print theme ('submit', array(
      '#value' =>'Вернуться',
      '#name' => 'pereschitat',));
    print theme ('submit', array(
      '#value' =>'Отправить заказ',
      '#name' => 'ofor',));
    print "
"
;
  }
  //Далее идёт обычный код табличного Vews с небольшими допиливаниями - касающимися преобразования форматов чисел, подсчёта и вывода "Итого:"
?>
  print$class; ?>" print $attributes; ?>>
    if (!empty($title)) : ?>
     

    endif; ?>
   

     
        foreach ($header as $field => $label): ?>
         

        endforeach; ?>
     

   
   
      foreach ($rows as $count => $row): ?>
       
printimplode(' ', $row_classes[$count]); ?>">
          foreach ($row as $field => $content): ?>
           

          endforeach; ?>
       

      $total = $total + str_replace(" ","",$row[phpcode_2]); endforeach; ?>
       

         
         
         
       
   
 
print $title; ?>
print $fields[$field]; ?>">
            print $label; ?>
         
print $fields[$field]; ?>">
             
                if (($_POST['otpr'] || $_POST['ofor']) && ($fields[$field]=='ops')) print "Заказано";  //если заявка на стадии оформления
                else print $content; //прячем флаги
              ?>
           
Предварительная общая стоимость заказа*:
           
              if (!$total) print "Пересчитайте";
              else print number_format (($total), 2,'.',' ')." р.";
            ?>
         
 


  if ($_POST['pereschitat'] || !$_POST) { //Если нажата кнопка пересчитать, или это вообще первый заход на страницу корзины - выводим кнопки
    print theme ('submit', array(
      '#value' =>'Пересчитать',
      '#name' => 'pereschitat',));
    print theme ('submit', array(
      '#value' =>'Оформить заказ',
      '#name' => 'otpr',));
  }
?>

Обновялю список шаблонов представлений, файл должен подцепиться.
Возвращаюсь к нашим полям Customfield. В них можно писать PHP код обращаясь к другим полям представления через конструкцию $data->"имя поля" список возможных полей можно посмотреть, если ввести туда временно print var_export($data, TRUE);

Вставляем туда такой код:


if (!$_POST) {// проверяем есть ли данные отправленные через форму
 $value=$_SESSION['korzina'][kolvo.$data->nid]; // если нет то берём их из сессии
}
 else {
$value=htmlspecialchars(stripslashes($_POST[kolvo.$data->nid])); // если да то выводим их пропустив через фильтры безопастности
}
?>
echo ($data->nid) // создаём поле с уникальным именем завязанным на Node ID ?>" size="10" value=" echo $value // Заполняем его предварительно введенным содержимым для удобств покупателя ?>" if (($_POST['ofor'] && is_numeric($a)) || ($_POST['otpr'])) echo "readonly style='border: 0px; background: none'" // Блокируем ввод в это поле если форма на стадии отправки ?> class="form-text required"> echo ($data->nid) ?>" value=' echo ($data->node_title) // Создаём скрытое поле с артикулом товара, для доступа к нему из шаблона, т.к. артикул в представлении я выключил, а подругому к нему обратиться не знаю как ?>'>

Для расчёта построчных сумм в следующем поле Customfield вставляем следующий текст:


print number_format (($_POST[kolvo.$data->nid] * $data->POLE_S_CENOY),2,'.',' ');
?>

Вот собственно и всё корзина оформляется в два этапа (Этап "пересчитать" не учитываю)
вводятся количества - нажимается "Оформить заказ", после этого, кстати, можно покидать страницу корзины - если данные корректны они сохраняются в сессию.

вводяться данные, нажимается "Отправить" - и заказ приходит по электропочте менеджеру.

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

И напоследок, огромная просьба всем:
Наверняка у данного решения есть недостатки и уязвимости. Я очень прошу всех кто понаходит критичные моменты, пожалуйста, не надо ломится радостно взламывать мой сайт (хотя я и пытался максимально убрать все связи с реальным проектом), лучше просто отпишите, что и как неправильно сделано, и я с нескрываемой признательностью к вам поправлю свои ошибки, а мир станет немножечко лучше.

Желающие перелопатить в модуль также велком ту - сделаем мир ещё немного лучше!

Если кто-то сочтёт достойным выложить на хабр, буду премного благодарен, с небольшой надеждой на инвайт.

ВложениеРазмер
Иконка простого текстового файла cart.tpl_.php_.txt9.41 КБ

Комментарии