Итак, наши пользователи могут отсылать заказы. Теперь наша задача - расширить панель управления так, чтобы администраторы могли просматривать список заказов, а пользователи - просматривать сделанные ими заказы.
Администратор должен иметь возможность:
При помощи этого шаблона мы будет выводить список заказов. Страница будет также содержать небольшую форму, которая позволит указать статус заказов, которые нужно отобразить. Для вывода списка заказов, мы будем использовать статический find()-метод Order :: findForAdmin(), который мы добавим чуть позже. В принпипе, аналогичную задачу мы решали при выводе списка товаров на фронтальной части для покупателей с возможностью поиска и фильтрации.
Файл shop/template/admin_order/display.html:
<core:set title='Orders'/> <core:WRAP file="admin_page.html" as="content"> <active_record:fetch using='src/model/Order' find='for_admin' target="orders" navigator='pager' order='date=DESC'/> <p/><b>Filter orders:</b> <form method="GET" id='filter_form' runat='server'> <?php $template->setChildDatasource('status_options_source', Order :: getStatusOptions()); ?> <select:options_source id='status_options_source' target='status' default_value='0' default_name='Show all'/> Filter : <select id='status' name='status' runat='server'></select> <input type='submit' name='filter' value="Filter" class='button'/><br/> </form> <core:include file='pager.html'/> <list:list id="orders"> <table cellpadding="0" cellspacing="0" class='list'> <thead> <tr> <th>Date</th> <th>Status</th> <th>Summ</th> <th>Actions</th> </tr> </thead> <list:item> <tr class='{$Parity}'> <td><route_url params="action:details,id:{$id}">{$date|date:"F j, Y, G:i"}</route_url></td> <td>{$status_name}</td> <td>${$summ|number:2, '.'}</td> <td> <route_url params="action:delete,id:{$id}">Delete</route_url> </td> </tr> </list:item> </table> </list:list> </core:wrap>
Обратите внимание вот на эти две строки:
<?php $template->setChildDatasource('status_options_source', Order :: getStatusOptions()); ?> <select:options_source id='status_options_source' target='status' default_value='0' default_name='Show all'/>
При помощи php-вставки мы передаем набор данных в тег <select:options_source>. Этот тег используется для заполнения списка опций тега <select>.
При помощи выражения {$date|date:«F j, Y, G:i»} мы вывели дату оформления заказа. Примененный фильтр date позволил явно указать формат вывода даты.
Также стоит отметить использование выражения {$status_name} - для вывода значения будет использован метод getStatusName(). С подобной практикой мы уже сталкивались при реализации отображения содержимого корзины.
Добавим метод findForAdmin() в класс Order.
Файл shop/src/model/Order.class.php:
<?php class Order extends lmbActiveRecord { [...] static function findForAdmin() { $toolkit = lmbToolkit :: instance(); $status = $toolkit->getRequest()->get('status'); if(!$status) return lmbActiveRecord :: find('Order'); else return lmbActiveRecord :: find('Order', 'status = ' . (int)$status); } [...] } ?>
Нам также потребуется метод Order :: getStatusOptions(), который будет возвращать список статусов, которые доступны для заказов.
<?php class Order extends lmbActiveRecord { [...] function getStatusOptions() { return array(self :: STATUS_NEW => 'New', self :: STATUS_PROCESSED => 'Processed', self :: STATUS_FINISHED => 'Delivered'); } [...] } ?>
При помощи этого шаблона мы будем отображать содержимое конкретного заказа, а также дадим администраторам возможность устанавливать новый статус заказа:
Файл shop/template/admin_order/details.html:
<core:set title='Single order'/> <core:WRAP file="admin_page.html" as="content"> <active_record:fetch using='src/model/Order' target="order" first='true'> <fetch:param record_id='{$#request.id}'/> </active_record:fetch> <core:datasource id="order"> <h2>Items</h2> <list:list from='lines'> <table cellpadding="0" cellspacing="0" class='list'> <thead> <tr> <th>Title</th> <th>Price</th> <th>Quantity</th> <th>Summ</th> </tr> </thead> <list:item> <tr class='{$Parity}'> <td>{$product.title}</td> <td>${$price|number:2, '.'}</td> <td>{$quantity}</td> <td>${$summ|number:2, '.'}</td> </tr> </list:item> </table> </list:list> <h2>Order status</h2> Status : {$status_name}<br/> Address : {$address}<br/> <h2>Set other order status</h2> <form id='form_status' name='form_status' method='post' runat='server'> <select:options_source from='^status_options' target='status' default_value='0' default_name='------'/> Status : <select id='status' name='status' runat='server'></select> <br/> <input type='submit' class='button' name='set_status' value="Set new status" class='button'/><br/> </form> <h2>Customer</h2> Name : {$user.name}<br/> Email : <a href='mailto:{$user.email}'>{$user.email}</a><br/> Login : {$user.login}<br/> </core:datasource> </core:wrap>
Посмотрите, как мы вывели список позиций заказа. Мы использования атрибут from тега <list:list>. Запись вида <list:list from='lines'> приводит к вызову метода get('lines') объекта Order, который вернет коллекцию (dataset) из связанных с заказом OrderLine.
Шаблон содержит форму, которая позволит администраторам менять статус заказа.
Обратите внимание на то, как мы заполнили список доступных статусов в данном случае - мы использовали атрибут from со значением «^status_options»:
<select:options_source from='^status_options' target='status' default_value='0' default_name='------'/> Status : <select id='status' name='status' runat='server'></select>
Почему именно так? <form> тег является контейнером данных, поэтому нам необходимо было использовать модификатор видимости на «1-уровень выше», а status_options приведет к вызову getStatusOptions() у объекта класса Order, который был загружен при помощи тега <active_record:fetch> и передан в тег <core:datasource>.
<?php lmb_require('src/model/Order.class.php'); class AdminOrderController extends lmbController { function doDisplay() { $this->setFormDatasource($this->request, 'filter_form'); } function doDelete() { try { lmbActiveRecord :: delete('Order', 'id = ' . $this->request->getInteger('id')); $this->redirect(); } catch(lmbARException $e) { $this->flashError('Wrond Order ID'); $this->redirect(array('controller' => 'admin')); return; } } function doDetails() { if(!$this->request->hasPost()) return; try { $order = new Order($this->request->getInteger('id')); } catch(lmbARException $e) { $this->flashError('Wrond Order ID'); $this->redirect(array('controller' => 'admin')); return; } $status = $this->request->get('status'); $this->flashMessage('Order status was changed'); $order->setStatus($status); $order->save(); } } ?>
Надеемся, что все в данном классе для вас уже знакомо и пояснений не требует.
Последний небольшой шаг - добавление возможности покупателям просматривать свои заказы.
Для этого мы добавим 2 шаблона profile/orders.html и profile/show_order.html и добавим метод doShowOrder(), где мы будем проверять, имеет ли пользователь право просматривать определенный заказ.
Файл shop/template/profile/orders.html:
<core:set title='Your orders'/> <core:wrap file="page.html" in="content"> <list:list from='#user.orders'> <table cellpadding="0" cellspacing="0" class='list'> <thead> <tr> <th>#</th> <th>Date</th> <th>Summ</th> <th>Status</th> </tr> </thead> <list:item> <tr class='{$Parity}'> <route_url_set field='order_url' params='action:show_order,id:{$id}'/> <td><a href='{$order_url}'>{$id}</a></td> <td><a href='{$order_url}'>{$date|date:"F j, Y, G:i"}</a></td> <td>${$summ|number:2, '.'}</td> <td>{$status_name}</td> </tr> </list:item> </table> <list:default> You made no orders in our shop yet. </list:default> </list:list> </core:wrap>
Так как объект пользователя всегда доступен в корневом контейнере данных шаблона, мы можем получить список его заказов при помощи конструкции from='#user.orders'. Помните, мы добавили в класс User связь с заказами один-ко-многим под названием orders. Именно это и позволяет нам использовать подобную конструкцию прямо в шаблоне.
Мы решили реализовать получение данных в данном случае обычным для большинства разработчиков способом - передать их из контроллера. Было бы слишком небезопасно давать возможность пользователям иметь возможность просмотреть содержимого любого заказа, ведь идентификатор можно легко изменить.
Файл shop/template/profile/show_order.html:
<core:set title='Order details'/> <core:wrap file="page.html" in="content"> <list:list from='#order.lines'> <table cellpadding="0" cellspacing="0" class='list'> <thead> <tr> <th>Title</th> <th>Price</th> <th>Quantity</th> <th>Summ</th> </tr> </thead> <list:item> <tr class='{$Parity}'> <td>{$product.title}</td> <td>${$price|number:2, '.'}</td> <td>{$quantity}</td> <td>${$summ|number:2, '.'}</td> </tr> </list:item> </table> </list:list> </core:wrap>
Конструкция <list:list from='#order.lines'> заберет из корневого контейнера данных переменную order, которую мы поставим в шаблоне из контроллера чуть ниже, затем из order возьмет переменную lines. lines - это коллекция позиций заказа.
Добавим в ProfileController метод doShowOrder(), который будет загружать указанный заказ и проверять право доступа текущего пользователя к заказу:
<?php class ProfileController extends lmbController { [...] function doShowOrder() { try { $order = new Order($this->request->getInteger('id')); if(!$order->belongsToUser($this->toolkit->getUser())) { $this->flashError('You can see only your orders!'); $this->redirect('/'); } $this->view->set('order', $order); } catch(lmbARException $e) { $this->flashError('Can\'t load order!'); $this->redirect('/'); } } [...] } ?>
Теперь необходимо только добавить метод Order :: belongsToUser().
class Order extends lmbActiveRecord { [...] function belongsToUser($user) { return ($this->getUserId() == $user->getId()); } [...] }
Пока это все, о чем мы хотели рассказать Вам о Limb3 в рамках данного примера.
У нас есть для Ваc еще один шаг: Шаг10. Рекомендации по дальнейшему изучению.
Обсуждение