Корзина магазина без 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 сохраняю его в папке темы с именем подсказанным в окне интерфейса, вот в этом шаблоне и будет практически весь механизм работы корзины, прикрепил к статье, но привожу листинг ниже:

<?php
  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)).'<br />';      // конвертируем это всё в текст т.к. если посылать темизированную таблицу то она почему то вставляется в ексель ввиде одного столбца с текстом из ячеек таблицы, как побороть х.з.
      }
      drupal_mail_send (array (                                               // посылаем письмо с заказом
      'id' => 'cart',
      'to' => '<vasya@pupkin.ru>',
      'subject' => 'из корзины',
      'body' => "<html>".$body."E-mail:".$_POST['email']."<br />"."Телефон:".$_POST['phone']."<br />"."Прочее:".$_POST['info']."<br />"."</html>",
      '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 '<p class="error">'.$err_msg.'</p>';
  if (!empty($msg)) print '<p class="ok">'.$msg.'</p>';
?>
<form name="kolvo_in_cart" method="post" action="/cart" class="cart">
<?php                                              //Добавляем форму с контактными данными
  if ($_POST['otpr']) {                            //в случае если нажата кнопка "Оформить" и никаких ошибок с количествами нет.
    print "<div class='korzina'>";
    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 "</div>";
  }
  //Далее идёт обычный код табличного Vews с небольшими допиливаниями - касающимися преобразования форматов чисел, подсчёта и вывода "Итого:"
?>
  <table class="<?php print $class; ?>"<?php print $attributes; ?>>
    <?php if (!empty($title)) : ?>
      <caption><?php print $title; ?></caption>
    <?php endif; ?>
    <thead>
      <tr>
        <?php foreach ($header as $field => $label): ?>
          <th class="views-field views-field-<?php print $fields[$field]; ?>">
            <?php print $label; ?>
          </th>
        <?php endforeach; ?>
      </tr>
    </thead>
    <tbody>
      <?php foreach ($rows as $count => $row): ?>
        <tr class="<?php print implode(' ', $row_classes[$count]); ?>">
          <?php foreach ($row as $field => $content): ?>
            <td class="views-field views-field-<?php print $fields[$field]; ?>">
              <?php
                if (($_POST['otpr'] || $_POST['ofor']) && ($fields[$field]=='ops')) print "Заказано";  //если заявка на стадии оформления
                else print $content; //прячем флаги
              ?>
            </td>
          <?php endforeach; ?>
        </tr>
      <?php $total = $total + str_replace(" ","",$row[phpcode_2]); endforeach; ?>
        <tr class="total">
          <td colspan="6">Предварительная общая стоимость заказа*:</td>
          <td class="views-field views-field-phpcode-2">
            <?php
              if (!$total) print "Пересчитайте";
              else print number_format (($total), 2,'.',' ')." р.";
            ?>
          </td>
          <td>&nbsp</td>
        </tr>
    </tbody>
  </table>
<?php
  if ($_POST['pereschitat'] || !$_POST) { //Если нажата кнопка пересчитать, или это вообще первый заход на страницу корзины - выводим кнопки
    print theme ('submit', array(
      '#value' =>'Пересчитать',
      '#name' => 'pereschitat',));
    print theme ('submit', array(
      '#value' =>'Оформить заказ',
      '#name' => 'otpr',));
  }
?>
</form>

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

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

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

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

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

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

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

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

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

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

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

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

Комментарии