Я давно мечтал добавить в один из своих проектов возможность заказа товаров через сайт, там был просто примитивный каталог с артикулами и ценами, заказчику приходилось звонить - перечислять позиции, либо, порыскав по сайту, скопировать нужные позиции в эл. письмо и отправить заказ. Всё это было долго и неудобно.
Про 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)).'<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> </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);
Вставляем туда такой код:
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 вставляем следующий текст:
Вот собственно и всё корзина оформляется в два этапа (Этап "пересчитать" не учитываю)
вводятся количества - нажимается "Оформить заказ", после этого, кстати, можно покидать страницу корзины - если данные корректны они сохраняются в сессию.
вводяться данные, нажимается "Отправить" - и заказ приходит по электропочте менеджеру.
Если на каком то из этапов что, то не то введено, возвращаем покупателя на предыдущий этап и ругаемся.
И напоследок, огромная просьба всем:
Наверняка у данного решения есть недостатки и уязвимости. Я очень прошу всех кто понаходит критичные моменты, пожалуйста, не надо ломится радостно взламывать мой сайт (хотя я и пытался максимально убрать все связи с реальным проектом), лучше просто отпишите, что и как неправильно сделано, и я с нескрываемой признательностью к вам поправлю свои ошибки, а мир станет немножечко лучше.
Желающие перелопатить в модуль также велком ту - сделаем мир ещё немного лучше!
Если кто-то сочтёт достойным выложить на хабр, буду премного благодарен, с небольшой надеждой на инвайт.
Вложение | Размер |
---|---|
cart.tpl_.php_.txt | 9.41 КБ |
Комментарии
спасибо, очень полезная информация.