Форум пользователей Impera CMS
Impera CMS - отличный движок для лёгкого создания интернет магазина.
Обладает невероятным количеством функций, необходимых в онлайн торговле.

Следить
Главная
12:52
29 авг
#
написал:

В дополнение к теме Пишем простой админ модуль Hello World с примером формы ввода рассмотрю еще один важный вопрос - создание модификации уже существующего модуля. То есть построить свой модуль на базе другого, не испортив исходный модуль.

Предположим, задача стоит такая: нужно добавить в таблицу products базы данных товаров несколько своих колонок

  • строковое поле email - емейл поставщика
  • строковое поле phone - телефон торгового агента
  • строковое поле addr - адрес поставщика
  • булевое поле secondhand - флажок "товар бывший в употреблении"
  • вещественное поле diagnosis - экспертная оценка внешнего вида товара
  • целочисленное поле tenancy - срок аренды в днях
  • поле даты postdate - дата публикации товарного предложения

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

Похожие задачи часто возникают и в других движках. Но поскольку Impera CMS обладает высокой внутренней сложностью, предусмотрительно скрытой от неопытных глаз в глубине движка, и поставляет на верхние программные уровни множество необычных возможностей, то здесь такие задачи решаются гораздо быстрее и на редкость универсально.


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

На измененный модуль в принципе можно было бы возложить обязанность отслеживать наличие в таблице необходимых дополнительных колонок и создавать их автоматически, если не найдены. В Impera CMS существуют такие функции, но пока мы отбросим этот вопрос, чтобы до некоторой поры не усложнять код модуля. Вместо этого представим, будто администратор базы данных с помощью phpMyAdmin уже создал следующие колонки в таблице products, и будто эти колонки с настоящего момента всегда существуют в базе товаров:

  • email - строка
  • phone - строка
  • addr - строка
  • secondhand - флажок
  • diagnosis - вещественное число
  • tenancy - число
  • postdate - штамп времени


Ясно, что поскольку движок не контролирует дополнительные колонки и на клиентскую сторону отправляет их в том виде, как нашел в записях базы данных, то на клиентской стороне эти сведения вывести в карточке товара можем так, например на странице товара (в шаблоне за ее вид отвечает файл product.tpl, в котором доступна переменная $product с полями записи текущего просматриваемого товара):

Здесь показан также и пример вывода нескольких стандартных важных полей, которыми обычно интересуются верстальщики, но не все знают их обозначения.


{* файл product.tpl - страница товара *}

    {$rate = $currency->rate_from / $currency->rate_to}

    <h1> {$product->model} </h1>

        <h2> Фотография </h2>
        <img class="photo" src="{$product->large_image|escape}">

        <h3> Дополнительные фото </h3>
        {foreach $product->fotos as $foto}
            <img class="thumbnail" src="{$foto->filename|escape}">
        {/foreach}

        <h4> Описание </h4>
        {$product->body}

        <h4> Характеристики </h4>
        <div class="properties">
            {foreach $product->properties_tree as $property}
                {$property1 = array_shift($property)}
                {if !empty($property1->in_product)}
                    {$property1->name}: {$property1->value|default:'-'}
                        {* и остальные значения свойства *}
                        {foreach $property as $propertyN} ● {$propertyN->value|default:'-'} {/foreach}
                {/if}
            {/foreach}
        </div>

        <div> Поступит в продажу: {$product->coming} </div>
        <div> Код производителя:  {$product->pcode} </div>
        <div> Гарантия:           {$product->guarantee} </div>
        <div> Категория:          {$product->category} </div>
        <div> Бренд:              {$product->brand} </div>

        <h5> Варианты товара </h5>
        {foreach $product->variants as $variant}
            <div class="variant">
                Название:  {$variant->name} <br>
                Артикул:   {$variant->sku} <br>
                На складе: {$variant->stock} шт. <br>
                Цена:      {($variant->discount_price * $rate)|string_format:'%1.2f'} {$currency->sign}
                <a class="button" href="/cart/add/{$variant->variant_id|escape}"> В корзину </a>
            </div>
        {/foreach}

        <h6> SEO текст </h6>
        {$product->seo_description}

    {* выводим наши дополнительные поля *}

    <h6> Поставщик </h6>
    <div class="supplier">
        Емейл:              {$product->email} <br>
        Телефон:            {$product->phone} <br>
        Адрес:              {$product->addr} <br>
        Был в употреблении: {($product->secondhand) ? 'Да' : 'Нет'} <br>
        Оценка вида:        {$product->diagnosis} <br>
        Срок аренды:        {$product->tenancy} <br>
        Дата предложения:   {$product->postdate}
    </div>


Теперь вернемся непосредственно к задаче. Исходный модуль имеет название Product и всегда доступен в админпанели по ссылке http://сайт/admin?section=Product.

Мы напишем модуль Product2, который породим от исходного модуля, сохраним изменившуюся часть кода в файле Product2.admin.php и разместим в папке http://сайт/objects/product2. В сущности модуль перекроет два метода (posting и update) родительского модуля, чтобы вклиниться в процесс обработки отправленной формы товара, и добавит пять своих небольших методов, чтобы дополнить эту обработку редакторским контролем новых полей:

  • isMyFields
  • collectMyFields
  • validateMyFields
  • updateMyFields
  • isFormPosted

И еще предоставляет свойство myFields, где можете указать (исправить на) свой список новых полей, который интересует уже исходя из вашей задачи. Все эти поля будут автоматически попадать в обработку редактирования записи о товаре.

Соответственно этот модуль будет всегда доступен в админпанели по ссылке http://сайт/admin?section=Product2 (если интересует транслировать его на URL исходного модуля, чтобы полностью закрыть собой родителя, это решается с помощью соответствующих mod_rewrite-овых инструкций в корневом файле .htaccess админпанели). А полный код модуля представлен ниже:

<?php
    // подключаем класс товара
    require_once(dirname(__FILE__) . '/../Admin.Products.php');

    // =======================================================================
    /**                                                                       
    *  Наша модификация стандартного админ модуля страницы товара             
    */                                                                        
    // =======================================================================

    class Product2 extends Product {

        // ===================================================================
        /**
        *  Задаем список наших полей в формате:
        *      ТИПимя поля => дефолтное значение|текст ошибки для незаполненных
        *      где типы:
        *          ! = булевое поле
        *          : = поле даты
        *          $ = число с плавающей запятой
        *          # = целое число
        *          _ = строковое поле
        */
        // ===================================================================

        protected $myFields = array('_email'      => '|',
                                    '_phone'      => '|Нужно ввести телефон поставщика.',
                                    '_addr'       => '|',
                                    '!secondhand' => '0|',
                                    '$diagnosis'  => '5.00|Нужно указать оценку внешнего вида.',
                                    '#tenancy'    => '31|Нужно указать срок аренды в днях.',
                                    ':postdate'   => 'NOW|Нужно указать дату публикации.');

        // ===================================================================
        /**                                                                   
        *  Признак что в опубликованной форме есть все заявленные наши поля   
        *  для конкретной записи                                              
        *                                                                     
        *  @access  protected                                                 
        *  @param   integer $id     ИД отслеживаемой записи                   
        *  @return  boolean         TRUE если все поля найдены                
        */                                                                    
        // ===================================================================

        protected function isMyFields ( $id ) {
            if (empty($this->myFields) || !is_array($this->myFields)) return FALSE;
            foreach ($this->myFields as $field => $error) {
                $field = substr($field, 1);
                if (!isset($_POST[$field][$id])) return FALSE;
            }
            return TRUE;
        }

        // ===================================================================
        /**                                                                   
        *  Сбор наших полей для конкретной записи из опубликованной формы     
        *                                                                     
        *  @access  protected                                                 
        *  @param   integer $id     ИД отслеживаемой записи                   
        *  @param   object  $item   объект записи, куда добавить поля         
        *  @param   boolean $test   TRUE если это вызов для валидации полей   
        *  @return  void                                                      
        */                                                                    
        // ===================================================================

        protected function collectMyFields ( $id, & $item, $test = FALSE ) {
            if (!empty($this->myFields) && is_array($this->myFields)) {
                foreach ($this->myFields as $field => $error) {
                    $default = FALSE;
                    if (!$test) {
                        $default = explode('|', $error);
                        $default = $default[0];
                    }
                    $type = substr($field, 0, 1);
                    $field = substr($field, 1);
                    switch ($type) {
                        case '!':
                            $item->$field = $this->request->getPostRecordFieldAsBoolean($field, $id, $default);
                            break;
                        case ':':
                            if ($default == 'NOW') $default = null;
                            $item->$field = $this->request->getPostRecordFieldAsDate($field, $id, $default);
                            break;
                        case '$':
                            $item->$field = $this->request->getPostRecordFieldAsFloat($field, $id, $default);
                            break;
                        case '#':
                            $item->$field = $this->request->getPostRecordFieldAsInteger($field, $id, $default);
                            break;
                        case '_':
                        default:
                            $item->$field = $this->request->getPostRecordField($field, $id, $default);
                    }
                }
            }
        }

        // ===================================================================
        /**                                                                   
        *  Валидация наших полей для конкретной записи в опубликованной форме 
        *                                                                     
        *  @access  protected                                                 
        *  @param   integer $id     ИД отслеживаемой записи                   
        *  @return  boolean         TRUE если есть ошибка и нужен стоп        
        */                                                                    
        // ===================================================================

        protected function validateMyFields ( $id ) {

            // собираем из формы наши поля
            if (!empty($this->myFields) && is_array($this->myFields)) {
                $item = new stdClass();
                $this->collectMyFields($id, $item, TRUE);

                // проверяем заполнение обязательных полей
                foreach ($this->myFields as $field => $error) {
                    $error = explode('|', $error);
                    $error = isset($error[1]) ? trim($error[1]) : '';
                    if ($error != '') {
                        $type = substr($field, 0, 1);
                        $field = substr($field, 1);
                        if ($item->$field === FALSE) return $this->push_error($error);
                        switch ($type) {
                            case '!':
                                break;
                            case ':':
                                if ($item->$field == '') return $this->push_error($error);
                                break;
                            case '$':
                                if ($item->$field == 0) return $this->push_error($error);
                                break;
                            case '#':
                                if ($item->$field == 0) return $this->push_error($error);
                                break;
                            case '_':
                            default:
                                if ($item->$field == '') return $this->push_error($error);
                        }
                    }
                }
            }
            return FALSE;
        }

        // ===================================================================
        /**                                                                   
        *  Обновление наших полей в конкретной записи базы данных             
        *                                                                     
        *  @access  protected                                                 
        *  @param   integer $id     ИД обновляемой записи                     
        *  @param   object  $item   объект записи                             
        *  @return  void                                                      
        */                                                                    
        // ===================================================================

        protected function updateMyFields ( $id, & $item ) {
            if (!empty($this->myFields) && is_array($this->myFields)) {
                $query = '';
                foreach ($this->myFields as $field => $error) {
                    $field = substr($field, 1);
                    $query .= '`' . $field . '` = \'' . $this->db->query_value($item->$field) . '\', ';
                }
                $query = 'UPDATE `' . $this->dbtable . '` '
                       . 'SET ' . rtrim($query, ', ') . ' '
                       . 'WHERE `' . $this->dbtable_field . '` = \'' . $this->db->query_value($id) . '\'';
                $this->db->query($query);
            }
        }

        // ===================================================================
        /**                                                                   
        *  Признак что форма редактирования товаров опубликована              
        *                                                                     
        *  @access  protected                                                 
        *  @return  boolean         TRUE если опубликована                    
        */                                                                    
        // ===================================================================

        protected function isFormPosted () {
            $field = 'post';
            return isset($_POST[$field]) && is_array($_POST[$field])
                && empty($_POST['ignore_post']);
        }

        // ===================================================================
        /**                                                                   
        *  Обработка редактирования записей                                   
        *                                                                     
        *  @access  protected                                                 
        *  @param   string  $result_page    URL страницы возврата после успеха
        *  @return  boolean                 TRUE если есть ошибка и нужен стоп
        */                                                                    
        // ===================================================================

        protected function posting ( & $result_page ) {

            // если форма опубликована, проверяем наши поля
            if ($this->isFormPosted()) {
                $field = 'post_this_one';
                foreach ($_POST['post'] as $id => $value) {
                    if (!isset($_POST[$field]) || isset($_POST[$field][$id])) {
                        if ($this->isMyFields($id)) {
                            $cancel = $this->validateMyFields($id);
                            if ($cancel) return $cancel;
                        }
                    }
                }
            }

            // все ОК, тогда пусть родитель обработает редактирование записи
            return parent::posting($result_page);
        }

        // ===================================================================
        /**                                                                   
        *  Обновление записи в базе данных                                    
        *                                                                     
        *  @access  protected                                                 
        *  @param   object  $item   объект записи                             
        *  @return  integer         ИД измененного товара                     
        */                                                                    
        // ===================================================================

        protected function update ( & $item ) {

            // пусть родитель обновит запись в базе
            $post_id = !empty($item->product_id) ? $item->product_id : 0;
            $id = parent::update($item);

            // если обновил, собираем из формы наши поля и обновляем в базе
            if (!empty($id)) {
                if ($this->isMyFields($post_id)) {
                    $this->collectMyFields($post_id, $item);
                    $this->updateMyFields($id, $item);
                }
            }
            return $id;
        }
    }
?>

Написание ответа

Перед публикацией рекомендуется использовать Предпросмотр, чтобы увидеть конечный вид сообщения.


Обратите внимание! Для противодействия спаму новые посты форума проявляются с задержкой от нескольких минут, пока не пройдут модерацию.