Перейти к содержанию

Pascha

Разработчик
  • Постов

    85
  • Зарегистрирован

  • Посещение

Весь контент Pascha

  1. Еще один реальный кейс по услугам на данном дополнении https://dianachernyh.ru/ редизайн существующего https://www.xn--80aakgm5acx0cq1c.xn--p1ai/ почувствуйте разницу)))) И все это на Опенкарт! стоимость как "землячке" - 30 тыс руб. срок выполнения 1,5 дня вместе с наполнением и настройкой доп модулей.
  2. Актуально для Opencart 2. Открываем файл catalog\controller\product\product.php Находим строку $results = $this->model_catalog_product->getProductRelated($this->request->get[‘product_id’]); И после нее добавляем $results = array_slice($results, 0, 7); Где 7 — и есть то самое количество выводимых рекомендуемых товаров (лимит). После добавления обязательно обновить кеш модификаторов!!! Чтобы они показывались в случайном порядке (рандомно) Помните в старом добром винампе была кнопка шаффл (shuffle), для рандомного воспроизведения музыки?)) Короче нам нужно добавить между ними одну строку — заключить резалтс в шаффл, то бишь вот так: $results = $this->model_catalog_product->getProductRelated($this->request->get['product_id']); shuffle($results); $results = array_slice($results, 0, 4); Со статусом в наличии Если нужно исключить отсутствующие товары, то делаем так: Всё тот же файл catalog\model\catalog\product.php Находим метод getProductRelated И после AND p.status = ‘1’ добавляем AND p.quantity > 0
  3. Сначала в контроллере нужно прописать (зарегистрировать) переменную. То есть открываем файл catalog/controller/product/category.php, находим строчку 'name' => $result['name'], и после нее вставляет, то что нам необходимо зарегистрировать, то есть: для модели вставляем 'model' => $result['model'], а для артикула вставляем 'sku' => $result['sku'], Сохраняем, закрываем. Теперь открываем сам файл шаблона страницы категории catalog/view/theme/ВАША_ТЕМА/template/product/category.tpl И там, где нам нужно вывести модель, пишем: для 2-ки <?php echo $product['model']; ?> для 3-ки {{ product.model }} а там, где нужен артикул, пишем: для 2-ки <?php echo $product['sku']; ?> для 3-ки {{ product.sku }} Сохраняем, закрываем. Обазятельно!!! Очистите кеш модификаторов, а-то не заработает! По сути всё. Должно работать. Вывести артикул или модель на странице производителей — аналогично.
  4. Чтобы в Opencart определить страницу категории как главную (домашнюю), надо проделать следующее. Открываем файл .htaccess и после строки RewriteBase / Вставляем следующую строку RewriteRule ^/?$ index.php?route=product/category&path=111/ [L] где, 111 — это id категории, которую нужно определить как главную страницу, то есть нужно указать свою. Если установлен SeoPro Если установлен модуль SeoPro и правило не работает, а сервер отдает код ответа 301 вместо «внутреннего» редиректа с кодом 200, необходимо сделать следующее: Открываем файл \catalog\controller\common\seo_pro.php, метод validate(). private function validate() { if (isset($this->request->get['route']) && $this->request->get['route'] == 'error/not_found') { return; } if(empty($this->request->get['route'])) { $this->request->get['route'] = 'common/home'; } if (isset($this->request->server['HTTP_X_REQUESTED_WITH']) && strtolower($this->request->server['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { return; } if (isset($this->request->server['HTTPS']) && (($this->request->server['HTTPS'] == 'on') || ($this->request->server['HTTPS'] == '1'))) { $url = str_replace('&amp;', '&', $this->config->get('config_ssl') . ltrim($this->request->server['REQUEST_URI'], '/')); $seo = str_replace('&amp;', '&', $this->url->link($this->request->get['route'], $this->getQueryString(array('route')), 'SSL')); } else { $url = str_replace('&amp;', '&', substr($this->config->get('config_url'), 0, strpos($this->config->get('config_url'), '/', 10)) // leave only domain . $this->request->server['REQUEST_URI']); $seo = str_replace('&amp;', '&', $this->url->link($this->request->get['route'], $this->getQueryString(array('route')), 'NONSSL')); } if (rawurldecode($url) != rawurldecode($seo)) { $this->response->redirect($seo, 301); } } И добавляем исключение: $array_exclusion = [ 'http://site.com/' ]; if (rawurldecode($url) != rawurldecode($seo) && !in_array($url, $array_exclusion)) { $this->response->redirect($seo, 301); } Где site.com — это УРЛ вашей главной страницы сайта.
  5. Вариант использования данного дополнения для "боевого" сайта (макет) http://banket.paschaopencart.ru/home - главная страница http://banket.paschaopencart.ru/vasilyevskiy_ostrov -страница 1 зала http://banket.paschaopencart.ru/rileeva - страница 2 зала все дополнительные модули и дополнения интегрированы исключительно Шорткодами.
  6. 1. Обновление от 03.03.24 а) добавлена возможность загрузки видео и настройка фонового видео оффера 1-го экрана б) самостоятельный модуль "витрина лендингов" ( для установки через схемы) в) добавлены два дополнительных поля для кастомных css и js для каждо отдельно взятой страницы лендинга г) несущественные правки по верстке
  7. Просмотр файла Система лендингов Опенкарт 3 / Landing page system Opencart 3 Система лендингов для Опенкарт 3 позволяет создавать неограниченное количество страниц типа "лендинг" Подробнее в видео Демо Демо2 Админка demo/demo Добавил Pascha Добавлено 21.01.2024 Категория Меню, дизайн, внешний вид
  8. Система лендингов для Опенкарт 3 позволяет создавать неограниченное количество страниц типа "лендинг" Подробнее в видео Демо Демо2 Админка demo/demo
    3 700 руб.
  9. обратите внимание на "обертку" if (document.getElementById('politics').checked) то есть блок к примеру в <input type="checkbox" id="politics" onclick="check();" value="" autocomplete="off"/> а второй блок к примеру в <input type="checkbox" id="politicform" onclick="check();" value="" autocomplete="off"/> и вот Вам новая идея прописать скрипт для politicform <script> function check() { var submit = document.getElementsByName('submit')[0]; if (document.getElementById('politicform').checked) submit.disabled = ''; else submit.disabled = 'disabled'; } </script> либо еще проще <script> function check() { var submit = document.getElementsByName('submit')[0]; var notChecked = false; if (!document.getElementById('politics').checked) notChecked = true; if (!document.getElementById('politicform').checked) notChecked = true; if(!notChecked) submit.disabled = ''; else submit.disabled = 'disabled'; } </script>
  10. Дополнительные фото в категории как в товаре. Делаем галерею на странице категории Сегодня речь пойдет о небольшой доработке — вывод дополнительных фотографий категории. Зачем это нужно? Если у вас в магазине продается плитка или же ламинат, покупателю будет интересно посмотреть в самой категории фото интерьеров или коллекции, если категория идет как "коллекция". Для этого возьмем уже готовый функционал с товара. В форме редактирования товара есть вкладка Изображения в которой можно добавить дополнительные фото и они будут отображены в карточке товара. Для добавления такого же функционала в категорию для начала нам надо добавить дополнительную таблицу в базу данных product_image. Делаем запрос в базу через phpmyadmin: CREATE TABLE IF NOT EXISTS `oc_category_image` ( `category_image_id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL, `image` varchar(255) DEFAULT NULL, `sort_order` int(3) NOT NULL DEFAULT '0', PRIMARY KEY (`category_image_id`), KEY `category_id` (`category_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; В данном примере создается таблица с префиксом oc_ посмотрите, у вас может быть другой! После этого открываем файл модели admin/model/catalog/category.php и добавляем возможность сохранения фотографий в базу данных. Для этого ищем в функции addCategory и editCategory строку: if (isset($data['image'])) { перед ней вставляем: if (isset($data['category_image'])) { foreach ($data['category_image'] as $category_image) { $this->db->query("INSERT INTO " . DB_PREFIX . "category_image SET category_id = '" . (int)$category_id . "', image = '" . $this->db->escape($category_image['image']) . "', sort_order = '" . (int)$category_image['sort_order'] . "'"); } } Далее добавляем функцию выборки картинок категории, например перед строкой: public function getCategory($category_id) { Вставляем: public function getCategoryImages($category_id) { $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "category_image WHERE category_id = '" . (int)$category_id . "' ORDER BY sort_order ASC"); return $query->rows; } После этого у нас изображения уже записываются в базу с формы редактирования товара. Далее открываем контроллер admin/controller/catalog/category.php находим строку: $data['placeholder'] = $this->model_tool_image->resize('no_image.png', 100, 100); и после нее вставляем код обработки фото: // Images if (isset($this->request->post['category_image'])) { $category_images = $this->request->post['category_image']; } elseif (isset($this->request->get['category_id'])) { $category_images = $this->model_catalog_category->getCategoryImages($this->request->get['category_id']); } else { $category_images = array(); } $data['category_images'] = array(); foreach ($category_images as $category_image) { if (is_file(DIR_IMAGE . $category_image['image'])) { $image = $category_image['image']; $thumb = $category_image['image']; } else { $image = ''; $thumb = 'no_image.png'; } $data['category_images'][] = array( 'image' => $image, 'thumb' => $this->model_tool_image->resize($thumb, 100, 100), 'sort_order' => $category_image['sort_order'] ); } После этого открываем форму редактирования категории admin/view/template/catalog/category_form.tpl и ищем строку: <li><a href="#tab-data" data-toggle="tab"><?php echo $tab_data; ?></a></li> после нее добавляем код вкладки: <li><a href="#tab-image" data-toggle="tab">Изображения</a></li> Далее ищем строку: <p class="tab-pane" id="tab-design"> И перед ней вставляем блок вкладки с формой добавления изображений: <div class="tab-pane" id="tab-image"> <?php $button_remove = 'Удалить'; $button_image_add = 'Добавить'; ?> <div class="table-responsive"> <table id="images" class="table table-striped table-bordered table-hover"> <thead> <tr> <td class="text-left">Изображение</td> <td class="text-right"><?php echo $entry_sort_order; ?></td> <td></td> </tr> </thead> <tbody> <?php $image_row = 0; ?> <?php foreach ($category_images as $category_image) { ?> <tr id="image-row<?php echo $image_row; ?>"> <td class="text-left"><a href="" id="thumb-image<?php echo $image_row; ?>" data-toggle="image" class="img-thumbnail"><img src="<?php echo $category_image['thumb']; ?>" alt="" title="" data-placeholder="<?php echo $placeholder; ?>" /></a><input type="hidden" name="category_image[<?php echo $image_row; ?>][image]" value="<?php echo $category_image['image']; ?>" id="input-image<?php echo $image_row; ?>" /></td> <td class="text-right"><input type="text" name="category_image[<?php echo $image_row; ?>][sort_order]" value="<?php echo $category_image['sort_order']; ?>" placeholder="<?php echo $entry_sort_order; ?>" class="form-control" /></td> <td class="text-left"><button type="button" onclick="$('#image-row<?php echo $image_row; ?>').remove();" data-toggle="tooltip" title="<?php echo $button_remove; ?>" class="btn btn-danger"><i class="fa fa-minus-circle"></i></button></td> </tr> <?php $image_row++; ?> <?php } ?> </tbody> <tfoot> <tr> <td colspan="2"></td> <td class="text-left"><button type="button" onclick="addImage();" data-toggle="tooltip" title="<?php echo $button_image_add; ?>" class="btn btn-primary"><i class="fa fa-plus-circle"></i></button></td> </tr> </tfoot> </table> </div> </div> И после этого конечно оживляем все кнопки скриптом. Внизу файла перед строкой <script type="text/javascript"><!-- Вставляем этот код: <script type="text/javascript"><!-- var image_row = <?php echo $image_row; ?>; function addImage() { html = '<tr id="image-row' + image_row + '">'; html += ' <td class="text-left"><a href="" id="thumb-image' + image_row + '"data-toggle="image" class="img-thumbnail"><img src="<?php echo $placeholder; ?>" alt="" title="" data-placeholder="<?php echo $placeholder; ?>" /></a><input type="hidden" name="category_image[' + image_row + '][image]" value="" id="input-image' + image_row + '" /></td>'; html += ' <td class="text-right"><input type="text" name="category_image[' + image_row + '][sort_order]" value="" placeholder="<?php echo $entry_sort_order; ?>" class="form-control" /></td>'; html += ' <td class="text-left"><button type="button" onclick="$(\'#image-row' + image_row + '\').remove();" data-toggle="tooltip" title="<?php echo $button_remove; ?>" class="btn btn-danger"><i class="fa fa-minus-circle"></i></button></td>'; html += '</tr>'; $('#images tbody').append(html); image_row++; } //--></script> После таких правок у нас уже есть возможность в админке добавлять, редактировать и сохранять изображения в категории. После любых изменений в коде не забывайте обновлять кеш модификаторов (Дополнения — Менеджер дополнений — кнопка Обновить) Вывод фото на странице категории. Для вывода фото на странице категорий нам необходимо добавить функцию выборки изображений из базы данных в модель. Для этого открываем файл catalog/model/catalog/category.php и, например перед строкой: public function getCategories($parent_id = 0) { вставляем нашу функцию public function getCategoryImages($category_id) { $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "category_image WHERE category_id = '" . (int)$category_id . "' ORDER BY sort_order ASC"); return $query->rows; } После этого полученные данные надо обработать в контроллере, для этого идем в файл catalog/controller/product/category.php и перед строкой: $data['description'] = html_entity_decode($category_info['description'], ENT_QUOTES, 'UTF-8'); вставляем код обработки: $data['images'] = array(); $results = $this->model_catalog_category->getCategoryImages($category_id); if($results){ $this->document->addScript('catalog/view/javascript/jquery/magnific/jquery.magnific-popup.min.js'); $this->document->addStyle('catalog/view/javascript/jquery/magnific/magnific-popup.css'); foreach ($results as $result) { $data['images'][] = array( 'popup' => $this->model_tool_image->resize($result['image'], $this->config->get($this->config->get('config_theme') . '_image_popup_width'), $this->config->get($this->config->get('config_theme') . '_image_popup_height')), 'thumb' => $this->model_tool_image->resize($result['image'], $this->config->get($this->config->get('config_theme') . '_image_additional_width'), $this->config->get($this->config->get('config_theme') . '_image_additional_height')) ); } } После этого у нас в шаблон идет массив $images в котором находятся фотографии категории. Для отображения их на сайте надо сделать вывод в шаблоне. Для этого открываем файл catalog/view/theme/default/template/product/category.tpl и в нужном месте вставляем код: <?php if ($images) { ?> <p class="row images"> <?php foreach ($images as $image) { ?> <p class="col-xs-6 col-sm-6 col-md-3"><a class="thumbnail" href="<?php echo $image['popup']; ?>" title="<?php echo $heading_title; ?>"> <img src="<?php echo $image['thumb']; ?>" title="<?php echo $heading_title; ?>" alt="<?php echo $heading_title; ?>" /></a></p> <?php } ?> </p> <hr> <script> $(document).ready(function() { $('.images').magnificPopup({ type:'image', delegate: 'a', gallery: { enabled:true } }); }); </script> <?php } ?> Учтите, что у вас может быть другой шаблон. В примере указан шаблон default (выделен выше). После всех правок можете чистить кеш модификаторов, кеш модуля кеширования (если он есть) и радоваться результатам.
  11. Теперь всегда нужно чтоб на вашем сайте в любой форме обратной связи присутствовал чекбокс согласия на обработку персональных данных. Зачастую разработчики уже предусматривают данную приблуду в своих дополнениях... но что если нет? Что если для Вас проблематично поменять код самого модуля по каким то причинам? Самое простое- вставить несложный скрипт и сделать пару правок. Вот этим мы и займемся. Итак... мы открыли код вывода формы обратной связи и ориентируясь на кнопку кода отправки сообщения: как правило это кнопка (ссылка или button ) type="submit" *можно использовать для поиска по документу. Где то чуть выше вставляем <div class="form-chek"> <input type="checkbox" id="politics" onclick="check();" value="" autocomplete="off"/> Я согласен (-на) на обработку персональных данных </div> тем самым мы сделали то что требуется.... но... отправка сообщения возможна что при активном чекбоксе, что при неактивном.... "факир был пьян- и фокус не удался" )))) Потому мы еще не закончили и поработаем с самой кнопкой отправки сообщения из формы к примеру мы имеем <button type="submit" class="btn btn-lg btn-primary">Оставить заявку</button> чуть видоизменим ее на <button type="submit" name="submit" class="submit btn btn-lg btn-primary" disabled="">Оставить заявку</button> и вставим скрипт <script> function check() { var submit = document.getElementsByName('submit')[0]; if (document.getElementById('politics').checked) submit.disabled = ''; else submit.disabled = 'disabled'; } </script> тем самым... пока чекбокс не активен - кнопка отправки сообщений так же не кликабельна... и при активном/отмеченном чекбоксе кнопка отправки становится работоспособной... казалось бы мы уже можем остановиться на этом))) но.... есть и другие задачи: к примеру такие, как стилизовать собственно чекбокс под дизайн сайта ( по умолчанию активный чекбокс - синего цвета ( так его определяют браузеры и подтягивают стандартные стили) исправим это недоразумение одной строчкой #politics { accent-color: #FFCB4A; } и вот она уже стала того цвета что нам требовалось.
  12. Когда у тебя на хосте 3-4 сайта в разработке с последующим переносом на хостинг заказчика лишние папки (да еще и одноименные) вне пределах собственно разрабатываемого сайта вообще ни к месту. При чем переносить или оставить - ни каким образом не сказывается на работоспособности сайта и установленных ocmod...
  13. При входе в админ-панель сайта может всплывать окно безопасности: Как убрать это предупреждение: в файле admin\controller\common\dashboard.php заменить строку $data['security'] = $this->load->controller('common/security'); на $data['security'] = '';
  14. Настройка регионов в Opencart позволит локализовать интернет-магазин и задать для него нужные налоги, доставку и другие настройки. Создание стран и регионов в Opencart Чтобы создать страну потребуется перейти в нужный пункт в «Система»-«Локализация» после чего кликаем на «Добавить». Вводим: Название страны; Код ISO (2); Код ISO (3); Формат адреса; Индекс обязателен; Статус. В случае с регионами потребуется перейти в соответствующий пункт в том же разделе и также добавляем его. Регионы== Ваш населенный пункт привязанный к стране. В таком случае все что будет внесено будет отображаться в корзине и пр.
  15. значит интегрируйте код из мода напрямую в файлы Вашего шаблона...скорее всего мод не делает необходимых изменений в силу разницы искомых строк и их отсутствия в Вашем шаблоне
  16. Это расширение добавляет изображения продуктов в электронное письмо с подтверждением заказа. tik-product-photos-in-order-email-1-4.zip
  17. при размещении заказа владелец магазина получает то же электронное письмо, что и покупатель. opencart-copy-customer-order-confirmation-for-owner-master.zip tik-Copy-customer-order-confirmation-for-owner-1-2.zip tik-Copy-customer-order-confirmation-for-owner-1-0.zip
  18. Цель данного дополнения - привлечение внимания покупателей графиком изменения цены с течением времени. По типу "успей купить, пока подешевело" Формируется из настроек в карточке товара (если поля не заполнены - модуль не отображается) Все данные настраиваются по желанию. График рисуется автоматически согласно указанным ценам. Используются свои таблицы в БД. Устанавливается из админки посредством ocmod. Заполняемые поля поддерживают как цифровые значения, так и текстовые ( к примеру для указания не года, а месяца) . Возможно использовать и то и другое ( к примеру используется сначала год (число), а потом месяца (текст)) Дополнительно: 1. При наведении на период (год/месяц - отображается стоимость товара в данный период) 2. При наведении на "точку" на графике отображается временной период и стоимость) Особенности: Не рекомендуется использовать одновременно более 6-ти значений для более корректного отображения. Язык: Русский Спасибо за идею в создании дополнения Петра Ширшова. DEMO
    1 500 руб.
  19. Просмотр файла График изменения цены товара / Product price change schedule Цель данного дополнения - привлечение внимания покупателей графиком изменения цены с течением времени. По типу "успей купить, пока подешевело" Формируется из настроек в карточке товара (если поля не заполнены - модуль не отображается) Все данные настраиваются по желанию. График рисуется автоматически согласно указанным ценам. Используются свои таблицы в БД. Устанавливается из админки посредством ocmod. Дополнительно: 1. При наведении на период (год/месяц - отображается стоимость товара в данный период) 2. При наведении на "точку" на графике отображается временной период и стоимость) Особенности: Не рекомендуется использовать одновременно более 6-ти значений для более корректного отображения. Язык: Русский Спасибо за идею в создании дополнения Петра Ширшова. DEMO Добавил Pascha Добавлено 12.11.2022 Категория Цены, скидки, акции, подарки  
  20. Криптовалюты все больше и больше входят в нашу жизнь. И если еще пару лет назад многие воспринимали их как «пустышку», то сегодня уже начинают понимать перспективу. Многие магазины начинают принимать в качестве оплаты за свои услуги криптовалюту, в частности bitcoin. Я сегодня не буду рассматривать как добавить BTC в качестве валюты и как получать их курсы в автоматическом режиме. Сегодня речь пойдет как вывести рядом с основной ценой в категориях и в карточке товара цену в биткоинах. Доработки будем делать в контроллерах, добавив новую переменную. Для получения курса будем использовать сервис bitpay.com, в частности его API. Я не буду детально разбирать сам скрипт. Он ниже $url = "https://bitpay.com/api/rates"; $json = json_decode(file_get_contents($url)); $dollar = $btc = 0; foreach($json as $obj){ if ($obj->code=='USD') { $kursbtc = (1/$obj->rate)*$result['price']; $kursbtc = number_format($kursbtc, 8, ".", ""); } } Идем в /catalog/product/category.php и после строки $price = $this->currency->format($this->tax->calculate($result['price'], $result['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); вставляем код, который я дал выше. Для цены со скидкой код будет чуть другим $url = "https://bitpay.com/api/rates"; $json = json_decode(file_get_contents($url)); $dollar = $btc = 0; foreach($json as $obj){ if ($obj->code=='USD') { $kursbtc_spec = (1/$obj->rate)*$result['special']; $kursbtc_spec = number_format($kursbtc_spec, 8, ".", ""); } } Далее после 'price' => $price, 'special' => $special, Добавляем 'kursbtc' => $kursbtc, 'kursbtc_spec' => $kursbtc_spec, По аналогии доработки делаются и для модулей Последние, Новинки, Рекомендуемые, Акционные В темплейте этих модулей в необходимом месте добавляем код вывода цены в bitcoin <?php echo $product['kursbtc']; ?> <?php echo $product['kursbtc_spec']; ?> Как вывести цену товара в BTC в карточке товара Теперь переходим к варточке товара. Опять идем в контроллер по адресу /catalog/product/product.php и после $data['price'] = $this->currency->format($this->tax->calculate($product_info['price'], $product_info['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); добавляем код $url = "https://bitpay.com/api/rates"; $json = json_decode(file_get_contents($url)); $dollar = $btc = 0; foreach($json as $obj){ if ($obj->code=='USD') { $data['kursbtc'] = (1/$obj->rate)*$result['special']; $data['kursbtc'] = number_format($data['kursbtc'], 8, ".", ""); } } И так же по аналогии делаем доработку для товаров со скидкой $url = "https://bitpay.com/api/rates"; $json = json_decode(file_get_contents($url)); $dollar = $btc = 0; foreach($json as $obj){ if ($obj->code=='USD') { $data['kursbtc_spec'] = (1/$obj->rate)*$result['special']; $data['kursbtc_spec'] = number_format($data['kursbtc_spec'], 8, ".", ""); } } Теперь идем в темплейт карточки товара и в нужном месте выводим стоимость в биткоинах <?php echo $kursbtc; ?> <?php echo $kursbtc_spec; ?> Не забываем после всех этих изменений обновить или почистить кеш модификаторов. Если Все было сделано верно, то результат должен быть примерно такой:
  21. Предположим, вы продаете в своем интернет магазине какой то штучный товар, который покупается исключительно в единичном экземпляре. В таком случае для увеличения вероятности покупки, желательно снизить количество действий, которые надо выполнить пользователя для перехода на страницу оформления заказа. В идеале, сразу после добавления товара в корзину, отправлять покупателя на оформление. Сегодня я разберу два варианта, как это можно сделать: исправим существующую кнопку добавления в корзину и добавим отдельно кнопку для быстрой покупки (Важно! Для товаров, у которых есть опции, данное решение в текущем виде не подойдет, так как перед этим необходимо вывести выбор опций в товары в категориях) Переход на страницу оформления заказа по нажатию на стандартную кнопку «Купить» Изменения будут вноситься в файле common.js (рассматривается на примере стандартного шаблона) для категорий, модулей, поиска и т.д. и в темплейте карточки товара (непосредственно для товара) Открываем файл /catalog/view/javascript/common.js Находим примерно на строке 140 функцию добавления в корзину 'add': function(product_id, quantity) { Здесь необходимо заменить поведение при нажатии на кнопку, а именно строчку $('html, body').animate({ scrollTop: 0 }, 'slow'); меняем на window.location.href = '/index.php?route=checkout/checkout'; Если у Вас установлен модуль упрощенного оформления заказа Simple, то код будет немного отличаться window.location.href = '/index.php?route=checkout/simplecheckout'; Сохраняем и обновляем кеш модификаторов. Теперь на страницах категорий, поиска, производителей и модулей при добавлении товара в корзину будет автоматический редирект на страницу оформления заказа. Чтобы это работало и в карточке товара, открываем темплейт по адресу /catalog/view/theme/default/template/product/product.tpl И примерно на 459 строке $('html, body').animate({ scrollTop: 0 }, 'slow'); меняем на window.location.href = '/index.php?route=checkout/checkout'; Если у Вас установлен модуль упрощенного оформления заказа Simple, то код будет немного отличаться window.location.href = '/index.php?route=checkout/simplecheckout'; Так же не забываем обновлять модификаторы. Добавляем новую кнопку перехода на страницу оформления заказа при покупке Здесь помимо изменений в файле скриптов, будут вноситься изменения и в темплейты Прежде всего добавим новую функцию в /catalog/view/javascript/common.js А именно после функции 'add': function(product_id, quantity) { после 176 строки добавляем 'addnew': function(product_id, quantity) { $.ajax({ url: 'index.php?route=checkout/cart/add', type: 'post', data: 'product_id=' + product_id + '&quantity=' + (typeof(quantity) != 'undefined' ? quantity : 1), dataType: 'json', beforeSend: function() { $('#cart > button').button('loading'); }, complete: function() { $('#cart > button').button('reset'); }, success: function(json) { $('.alert, .text-danger').remove(); if (json['redirect']) { location = json['redirect']; } if (json['success']) { $('#content').parent().before('<div class="alert alert-success"><i class="fa fa-check-circle"></i> ' + json['success'] + ' <button type="button" class="close" data-dismiss="alert">&times;</button></div>'); // Need to set timeout otherwise it wont update the total setTimeout(function () { $('#cart > button').html('<span id="cart-total">' + json['total'] + '</span>'); }, 100); window.location.href = '/index.php?route=checkout/checkout'; $('#cart > ul').load('index.php?route=common/cart/info ul li'); } }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); } }); }, На сколько можете видеть, мы добавили полную копию функции с незначительными изменениями. Далее идем в темплейты категорий, страницы поиска и т.д. и там добавляем новую кнопку. Рассмотрим на примере категорий /catalog/view/theme/default/template/product/category.tpl Находим примерно на строке 131 код <button type="button" onclick="cart.add('<?php echo $product['product_id']; ?>', '<?php echo $product['minimum']; ?>');"><i class="fa fa-shopping-cart"></i> <span class="hidden-xs hidden-sm hidden-md"><?php echo $button_cart; ?></span></button> и перед ним или после добавляем такой <button type="button" onclick="cart.addnew('<?php echo $product['product_id']; ?>', '<?php echo $product['minimum']; ?>');"><i class="fa fa-plane"></i> <span class="hidden-xs hidden-sm hidden-md"><?php echo $button_cart; ?></span></button> Сохраняем, обновляем модификаторы и проверяем. Если все сделали верно, то должно работать. Для модулей и других страниц дейтвия идентичны. Для карточки товара в /catalog/view/theme/default/template/product/product.tpl также добавляем новую функцию на строке 470 после стандартной функции добавления в корзину <script type="text/javascript"><!-- $('#button-cartnew').on('click', function() { $.ajax({ url: 'index.php?route=checkout/cart/add', type: 'post', data: $('#product input[type=\'text\'], #product input[type=\'hidden\'], #product input[type=\'radio\']:checked, #product input[type=\'checkbox\']:checked, #product select, #product textarea'), dataType: 'json', beforeSend: function() { $('#button-cart').button('loading'); }, complete: function() { $('#button-cart').button('reset'); }, success: function(json) { $('.alert, .text-danger').remove(); $('.form-group').removeClass('has-error'); if (json['error']) { if (json['error']['option']) { for (i in json['error']['option']) { var element = $('#input-option' + i.replace('_', '-')); if (element.parent().hasClass('input-group')) { element.parent().after('<div class="text-danger">' + json['error']['option'][i] + '</div>'); } else { element.after('<div class="text-danger">' + json['error']['option'][i] + '</div>'); } } } if (json['error']['recurring']) { $('select[name=\'recurring_id\']').after('<div class="text-danger">' + json['error']['recurring'] + '</div>'); } // Highlight any found errors $('.text-danger').parent().addClass('has-error'); } if (json['success']) { $('.breadcrumb').after('<div class="alert alert-success">' + json['success'] + '<button type="button" class="close" data-dismiss="alert">&times;</button></div>'); $('#cart > button').html('<span id="cart-total"><i class="fa fa-shopping-cart"></i> ' + json['total'] + '</span>'); window.location.href = '/index.php?route=checkout/checkout'; $('#cart > ul').load('index.php?route=common/cart/info ul li'); } }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); } }); }); //--></script> Ну и после этого добавляем новую кнопку. Примерно на строке 296 после или перед <button type="button" id="button-cart" data-loading-text="<?php echo $text_loading; ?>" class="btn btn-primary btn-lg btn-block"><?php echo $button_cart; ?></button> Добавляем новую кнопку <button type="button" id="button-cartnew" data-loading-text="<?php echo $text_loading; ?>" class="btn btn-primary btn-lg btn-block"><i class="fa fa-plane"></i></button> Так же сохраняем, обновляем модификаторы и радуемся результату.
  22. Не редко в интернет магазине бывает такая ситуация, когда указать актуальную цену невозможно. Например, товар идет только под заказ или цена на товар меняется довольно часто. У таких товаров цену обычно ставят нулевой и указывают что товар может быть только под заказ. Для клиента цена в ниже нуля выглядит не особо красиво и в ряде случаев может привести к тому, что потенциальный клиент просто уйдет от Вас. Лучшим решением данной проблемы будет вывод «заглушки», т.е. какой то надписи, например «Уточняйте цену». В идеале конечно делать интерактивное окно запроса уточнения цены, однако это не тема сегодняшней статьи. Изменения необходимо будет делать в языковых файлах, контроллерах и темплейтах модулей, категорий, странице поиска, странице производителя, списке акционных товаров и непосредственно в карточке товара. Я рассмотрю на примере категорий и карточки товара. Остальные страницы делаются по аналогии с категориями. Заменяем нулевую цену в категориях опенкарт на «Уточнить цену» Прежде всего открываем контроллер категорий. В нем добавим языковую переменную, так как в случае мультиязычного магазина прописывать текст в темплейте не верно. catalog/controller/product/category.php после $data['text_limit'] = $this->language->get('text_limit'); Добавим $data['text_nullpprice'] = $this->language->get('text_nullpprice'); Тут же добавим новую переменную, чтобы определять «чистую» цену. Находим $price = $this->currency->format($this->tax->calculate($result['price'], $result['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); И под ней добавляем $pricenull = $result['price']; Далее ищем 'price' => $price, и сразу за нм добавляем 'pricenull' => $pricenull, Теперь добавим переменную в языковой файлах catalog/language/ru-ru/product/category.php $_['text_limit'] = 'Показывать:'; Добавляем $_['text_nullpprice'] = 'Уточняйте цену'; Если в вашем магазине несколько языков, сделайте изменения в каждом из них с соответствующим переводом После переходим непосредственно к темплейту категорий по адресу catalog/view/theme/default/template/product/category.tpl(category.twig) Находим примерно на 108 строке конструкцию <?php if (!$product['special']) { ?> <?php echo $product['price']; ?> <?php } else { ?> и меняем ее на <?php if (!$product['special']) { ?> <?php if (product['pricenull'] > '0') { ?> <?php echo product['price']; ?> <?php } else { ?> <?php echo $text_nullpprice; ?> <?php } ?> <?php } else { ?> Для версии опенкарт 3.х Находим первое вхождение {{ product.price }} И меняем на {% if (product.pricenull > '0') %} {{ product.price }} {% else %} {{ product.text_nullpprice }} {% endif %} Заметьте, изменения делаем только для основной цены, так как нулевая цена не предусматривает скидок, это логично. Сохраняем, обновляем модификаторы и если все сделано верно, то у товаров с нулевой ценой будет выведен текст, который Вы указали в языковом файле. Для модулей и типовых страниц, таких как поиск, производители порядок действий будет идентичным. Заменяем нулевую цену в карточке опенкарт на «Уточнить цену» Как и в случае с категориями, переходим в контроллер catalog/controller/product/product.php и добавляем языковую переменную. После $data['text_loading'] = $this->language->get('text_loading'); Добавляем $data['text_nullpprice'] = $this->language->get('text_nullpprice'); Тут же находим $data['price'] = $this->currency->format($this->tax->calculate($product_info['price'], $product_info['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); И снизу добавляем $data['pricenull'] = $product_info['price']; Далее ищем $price = $this->currency->format($this->tax->calculate($result['price'], $result['tax_class_id'], $this->config->get('config_tax')), $this->session->data['currency']); И так же снизу добавляем $pricenull = $product_info['price']; И дальше ищем 'price' => $price, и ниже добавляем 'pricenull' => $pricenull, С контроллером закончили. Теперь необходимо ее прописать в языковом файле товара по адресу catalog/language/ru-ru/product/product.php После $_['text_year'] = 'год'; Добавляем $_['text_nullpprice'] = 'Уточняйте цену'; Для других языков делаем то же самое. Далее можно переходить к темплейту карточки товара catalog/view/theme/default/template/product/product.tpl(product.twig) Здесь изменения будем делать как для самого товара, так и для рекомендуемых товаров. Находим <?php if (!$special) { ?> <li> <h2><?php echo $price; ?></h2> </li> <?php } else { ?> И меняем на <?php if (!$special) { ?> <li> <h2><?php if (pricenull > '0') { ?><?php echo $price; ?><?php } else { ?><?php echo $text_nullpprice; ?><?php } ?></h2> </li> <?php } else { ?> Для версии 3.х Находим {% if not special %} <li> <h2>{{ price }}</h2> </li> {% else %} Меняем на {% if not special %} <li> <h2>{% if (pricenull > '0') %}{{ price }}{% else %}{{ text_nullpprice }}{% endif %} </h2> </li> {% else %} Теперь добавим доработку для рекомендуемых. Находим <?php if (!$product['special']) { ?> <?php echo $product['price']; ?> <?php } else { ?> И меняем на <?php if (!$product['special']) { ?> <?php if (product['pricenull'] > '0') { ?> <?php echo product['price']; ?> <?php } else { ?> <?php echo $text_nullpprice; ?> <?php } ?> <?php } else { ?> Для версии опенкарт 3.х Находим первое вхождение {{ product.price }} И меняем на {% if (product.pricenull > '0') %} {{ product.price }} {% else %} {{ product.text_nullpprice }} {% endif %} Сохраняем, обновляем модификаторы и проверяем результат. Если все сделано верно, вместо нулевой цены будет написано «Уточняйте цены».
  23. Думаю все в курсе, что в опенкарт можно задавать скидочные цены. При чем их можно задавать для разных групп покупателей. Обычно скидочная цена задается в зависимости от количества товара. Выводятся эти цены обычно под основной ценой в виде таблицы А знали ли вы, что если скидка задается при покупке от одной единицы покупки, то именно скидочная цена будет отображена вместо основной цены? Сегодня сделаем реализацию вывода розничной и оптовой цены для магазина, который помимо розничной торговли, занимается оптовой. Т.е. на сайте есть группа покупателей Оптовики. Все дело в том, что если для оптовых покупателей указать скидочную цену от 1 единицы товара, то будет выводиться только скидочная цена, и оптовик не будет видеть сколько он экономит, по сравнению с розничным покупателем. Сложного ничего нет и работа займет не больше пяти минут. Доработка делается только для карточки товара. Если Вам необходимо сделать подобную реализацию для категорий, страницы поиска, модулей, то делается это по аналогии. Прежде всего переходим в модель товара /catalog/model/catalog/product.php Примерно на 33 строке после 'price' => ($query->row['discount'] ? $query->row['discount'] : $query->row['price']), Добавляем 'standart_price' => $query->row['price'], Сохраняем и переходим к контроллеру по адресу /catalog/controller/product/product.php и после строки $data['price'] = $this->currency->format($this->tax->calculate($product_info['price'], $product_info['tax_class_id'], $this->config->get('config_tax'))); нам необходимо добавить основную цену. Назовем ее standart_price. Вывод стандартной цены будет выглядеть так $data['standart_price'] = $this->currency->format($this->tax->calculate($product_info['standart_price'], $product_info['tax_class_id'], $this->config->get('config_tax'))); Однако это не все. Так как шаблон карточки товара для всех групп пользователей один, то необходимо задать условие, при котором мы будем или не будем отдавать стандартную цену. Так как если этого не сделать, то в карточке товара для оптового покупателя все будет работать правильно, а вот для розничного будет выводиться две одинаковые цены на с разным названием) В итоге добавляемая конструкция будет выглядеть так if ($product_info['standart_price']==$product_info['price']) { $data['standart_price'] = false; } else { $data['standart_price'] = $this->currency->format($this->tax->calculate($product_info['standart_price'], $product_info['tax_class_id'], $this->config->get('config_tax'))); } Сохраняем и переходим к визуальной части, а именно к изменению шаблона карточки товара по адресу /catalog/view/theme/default/template/product/product.tpl Перед <?php if ($tax) { ?> Добавляем <?php if ($standart_price) { ?> <li> <h2>Розничная цена: <?php echo $standart_price; ?></h2> </li> <?php } ?> Такая конструкция верна для магазина с одним языком. Если же у Вас мультиязычный магазин, тогда вместо «Розничная цена:» необходимо добавить языковую переменную, прописать ее в контроллере, а в языковых файлах прописать значение. Сохраняем все изменения, обновляем модификаторы и если все сделано верно, то Вы должны увидеть что то такое
  24. Модификаторы в OpenCart - это разновидность модулей, позволяющих вносить изменения в функционал или внешний вид, при этом не затрагивая оригинальные файлы. Таким образом обеспечивается неизменность файлов самого opencart-а, но при этом мы можем менять или добавлять функционал. В этой статье я расскажу как создать свой модификатор для OpenCart и какие есть возможности у модификаторов. Изначально, история появления модификаторов началась с vQmod в версии 1.5 opencart-а, а затем его усовершенствовали и преобразовали в OCMOD, который стал постоянным стандартным функционалом OpenCart-а начиная с 2.1 версии. Итак, попробую дать определение понятию модификатор в OpenCart. Модификатор в OpenCart - это модуль представляющий собой XML-файл, содержащий набор инструкций о том в каких оригинальных файлах OpenCart-а, в каких местах, какой код нужно заменить, добавить или удалить. OpenCart при выполнении инструкций xml-файла создает копии исходных файлов в специальную директорию (кэш модификаторов) и вносит изменения в этих копиях. В результате, при работе сайта используются измененные файлы, а не оригиналы. Модификатор OCMOD может быть как самостоятельным модулем, изменяющим или дополняющим стандартный функционал, так и входить в состав полноценного модуля с дополнительными php, twig и другими файлами. О написании полного модуля читайте в предыдущей статье. Имена файлов и директории Имя файла модификатора - должно быть задано в соответствии с шаблоном <имя_файла>.ocmod.xml , где вместо <имя_файла> вы пишите свое название. Таким образом имя файла всегда должно заканчиваться ".ocmod.xml", иначе система не "увидит" модификатор и проигнорирует его. Исключение: когда предполагается использовать модификатор в составе полного модуля в виде архива - в этом случае имя файла-модификатора в архиве должно быть install.xml. Расположение файла модификатора - папка system в корне сайта. Файл-модификатор так же можно загрузить в базу данных на постоянное размещение с помощью загрузки расширений. Тем не менее, во время разработки удобнее записать файл в system и редактировать его там, проверяя какой получается результат, а когда разработка завершена, модификатор загружают как модуль в базу данных. Расположение кэша модификаторов - папка /storage/modification с такой же структурой, как корень сайта. Т.е. если вы сделали модификатор для файла /admin/controller/catalog/product.php то его измененная копия будет находится в /storage/modification/admin/controller/catalog/product.php. В нем вы и увидите те изменения которые производит ваш модификатор. Расположение логов - папка /storage/logs В ней есть 2 основных лог-фала: ocmod.log - который показывает как обрабатывались файлы-модификаторы, в какой последовательности и как выполнялись инструкции и лог-файл error.log - который содержит ошибки, произошедшие при преобразовании. Обновление кэша модификаторов Для того, чтобы система увидела файлы-модификаторы и выполнила их инструкции, необходимо нажать кнопку "Обновить" на странице модификаторы. Бывают ситуации, когда кэш обновили, а изменений на сайте так и не увидели. Обычно это связано с тем, что на сайте используются дополнительные модули кэширования или ускорители загрузки и т.п., в которых вероятно, тоже необходимо сделать обновление кэша. Синтаксис модификатора OCMOD Файл модификатора OCMOD должен содержать общую описательную часть и инструкции по изменению файлов. Начну разбор синтаксиса с простого примера, который добавляет слово "Test" в шапке администраторского раздела: <?xml version="1.0" encoding="utf-8"?> <modification> <name>Test</name> <!-- Название модификатора --> <code>Test</code> <!-- Уникальный код модификатора --> <version>1.0</version> <!-- Версия --> <author>Test</author> <!-- Автор --> <link>http://www.test.ru</link> <!-- Сайт разработчика --> <file path="admin/view/template/common/header.twig"> <!-- Какой файл будет модифицироваться --> <operation> <search> <!-- Найти код в файле --> <![CDATA[ <div class="container-fluid"> ]]> </search> <add position="after"> <!-- Добавление модификации (после найденного кода) --> <![CDATA[ <p>Тест</p> ]]> </add> </operation> </file> </modification> Итак, данный пример модификатора меняет файл header.twig. Он находит строчку <div class="container-fluid"> и после нее добавляет <p>Тест</p> В одном xml-файле может быть любое количество секций <file> и соответственно, мы можем менять множество файлов одним модификатором. Разберем каждый тег модификатора и его возможности поподробнее. File Указывает в каком файле или файлах нужно внести изменения. Обязательный атрибут path содержит путь до изменяемого файла. Может указывать на один файл или на несколько. Для указания нескольких файлов используется символ "|". Например, внести изменения в action.php и в loader.php <file path="system/engine/action.php|system/engine/loader.php"> Для сокращения кода можно использовать фигурные скобки, которые позволяют указать несколько значений через запятую: <file path="system/engine/{action,loader}.php"> Так же можно использовать символы "*" и "?", чтобы указать путь по "маске". Часто бывает полезно для модификации файлов-шаблонов. <file path="catalog/view/theme/*/template/product/product.twig"> Т.к. мы не знаем заранее, какие именно темы установлены в OpenCart, мы указали "*" после "theme" и тогда будут модифицироваться все product.twig во всех темах. Operation Указывает начало секции производимой модификации. Внутри File секций <operation> может быть несколько. Т.е мы можем делать сразу несколько изменений в одном файле. Тег Operation может иметь необязательный атрибут error, который может принимать значения: skip - в случае ошибки пропустить текущую секцию <operation> и перейти к следующей <operation> log (по-умолчанию) - в случае ошибки пропустить всю секцию <file> и перейти к следующему <file> abort - в случае ошибки прервать все модификации в xml-файле Например, найти в файле header.twig текст "navbar-rightnav" и если его нет, тогда пропустить и перейти к следующей операции и найти "navbar-right": <file path="admin/view/template/common/header.twig"> <operation error="skip"> <search><![CDATA[ navbar-rightnav ]]></search> <add position="after"><![CDATA[ <li>Test1</li> ]]></add> </operation> <operation error="skip"> <search><![CDATA[ navbar-right ]]></search> <add position="after"><![CDATA[ <li>Test2</li> ]]></add> </operation> </file> Если не указывать атрибут error="skip", тогда на первом поиске текста "navbar-rightnav" вся секция <file> была бы прервана и проигнорирована. Search Указывает какой текст необходимо найти в текущей операции. Есть несколько правил использования тега: Тег Search может быть использован только 1 раз внутри секции Operation. Поиск можно делать только 1 строки целиком или части строки (нельзя искать несколько строк одновременно). Искомый текст необходимо размещать между <![CDATA[ и ]]>. Пробелы и переносы строки до искомого текста и после искомого текста игнорируются (поэтому искомый текст можно написать или сразу после CDATA или с новой строки после CDATA, как больше нравится), если только не указан атрибут trim="false" (описание ниже). Изменения выполняются над всеми найденными в файле строками (или частями строк), если только не указан атрибут index (описание ниже). Специальные теги <![CDATA[ и ]]> - используются в xml-файлах для указания любых символьных данных, что означает, что между ними может находиться какой угодно текст, содержащий скобки, значи больше, меньше и другие, в т.ч. и php-код, html-код и т.д. Для более точного определения места внесения изменений в теге Search могут использоваться атрибуты: index - указывает в каком по-порядку найденном тексте сделать изменения. Т.е. если в файле искомый текст встречается несколько раз, тогда index позволяет указать номер найденного по порядку текста (0 - первый найденный текст, 1 - второй и т.д.) Можно так же указать несколько номеров через запятую. trim - указывает игнорировать (true) или нет (false) пробелы и переносы строк до и после искомого текста. regex - если установлено значении true, значит искомый текст представляет собой регулярное выражение для поиска. Пример: добавить пункт меню "ТЕСТ". <file path="admin/controller/common/column_left.php"> <operation> <search index="0" trim="true"><![CDATA[ $data['menus'][] = array( ]]></search> <add position="before"><![CDATA[ $data['menus'][] = array( 'id' => 'menu-test', 'icon' => 'fa-play', 'name' => 'TEST', 'href' => '#' ); ]]></add> </operation> </file> В данном примере находим первое "$data['menus'][] = array(" и перед ним добавляем свой код. Add Тег содержит текст, который будет добавлен до/после найденного или на который будет заменен найденный текст. Так же как и тег Search, должен содержать <![CDATA[ и ]]> между которыми пишется текст кода, который будет добавляться/заменяться. В теге Add могут использоваться атрибуты: position - может принимать значения: replace (по умолчанию) - замена найденного текста before - добавить текст перед найденным текстом after - добавить текст после найденного текста offset - означает смещение относительно найденного текста на указанное количество строк. Если position="before", тогда смещение будет вверх от найденного текста, если position="after" или position="replace", тогда смещение будет вниз от найденного текста. trim - указывает игнорировать (true) или нет (false) пробелы и переносы строк до и после искомого текста. Пример: Добавить слово "Test" в админ-панели в списке товаров. <file path="admin/view/template/catalog/product_list.twig"> <operation> <search index="1" trim="true"><![CDATA[ panel-body ]]></search> <add position="after" offset="1" trim="true"><![CDATA[ <p>Test</p> ]]></add> </operation> </file> Находим 2-й по порядку текст "panel-body" (первый - это фильтр справа от товаров, а второй - сам список товаров) и затем добавляем код "<p>Test</p>" на одну строку ниже найденного текста. Обратите внимание: position="before" и position="after" добавляют код не в середину строки, где было найдено "panel-body", а в следующую (или предыдущую) строку. Если нужно сделать добавление в середине строки, следует использовать position="replace" и повторив тот же самый найденный текст мы добавляем свой. Пример: Добавить текст перед версией opencart-а в футере <file path="admin/view/template/common/footer.twig"> <operation> <search><![CDATA[ {{ text_version }} ]]></search> <add position="replace" trim="true"><![CDATA[ <p>Test</p>{{ text_version }} ]]></add> </operation> </file> Создав модификатор OCMOD в OpenCart-е можно изменить практически всю систему, расширить ее возможности, при этом исходные файлы не будут затронуты и вы можете всё вернуть в первоначальный вид, просто убрав файл-модификатор (или отключив его, если он был загружен в базу данных).
×
×
  • Создать...