Перейти до вмісту
Пошук в
  • Детальніше...
Шукати результати, які ...
Шукати результати в ...

Блог spectre

  • записів
    6
  • коментарів
    149
  • перегляду
    4 844

Как написать OCMOD-модификатор чтобы он даже работал и ничего не сломать


spectre

8 231 перегляд

Сегодня мы разберем такую штуку как OCMOD-модификаторы, странно, но более-менее нормального мануала в сети нет, попробуем исправить этот недостаток так чтобы даже школьник понял как оно работает.

 

Какие-то неочевидные баги, особенности и приколы мы не будем рассматривать, оставим это удовольствие тем кто решит все-таки пойти дальше и писать свои модули :) 

 

Итак, OCMOD-модификатор это простой XML-файл, который изменяет PHP-файлы и/или tpl/twig- файлы шаблонов.

 

Вообще модификатор - это zip-архив с расширением ocmod.zip

в нем могут быть

папка upload - в которой файлы для загрузки на сервер

файл install.xml - сам XML-модификатор который изменяет файлы

файл install.php - php-файл который выполняется во время установки модификатора

иногда install.sql - то же самое, только для запросов в бд

 

Это очень небезопасная штука и 90% вирусни на опенкарте - это следствие того что украли админку и загрузили опасные файлы прямо через установщик расширений, я бы отключал вообще этот функционал, а для модулей существуют методы install и uninstall

 

Но мы будем рассматривать только модификатор, который меняет код в файлах и будем называть его OCMOD-модификатором

 

 

Как оно работает

 

Есть 2 варианта применить модификатор, первый - положить в папочку system файлик с расширением .ocmod.xml, второй - загрузить файл через установщик дополнений.

Первый вариант предпочтительнее, т.к. его легче править, прямо фтп-клиентом и можно уже обновлять кеш, в базе - есть всякие онлайн-редакторы, мне они не нравятся, потому что:

 

a) есть ограничение на размер файла (правится размером поля в бд, но можно все провтыкать);

б) иногда их блокируют всякие modsecurity;

в) просто тупо неудобно в браузере

 

Также нужно учитывать что порядок применения модификаторов таков - сначала применяются файлы из папки system по алфавиту, потом файлы из базы по названию или дате добавления

 

То есть что делать мы уже знаем, чтобы сделать модификатор нам надо сделать xml-файлик и положить его в папку system

 

Базовая структура OCMOD-файла такая

<?xml version="1.0" encoding="utf-8"?>
<modification>
<name>Name Of Mega Modification - название нашего супер модуля</name>
<code>name_of_mega_modification - внутренний код модификатора</code>
<version>1.0 2.3.x-3.0.x - можно написать версию файла, для каких версий подходит, ну так, чтоб понятно было</version>
<author>spectre - ваш супер ник</author>
<link>https://freelancer.od.ua/ - ваш суперсайт</link>

Здесь будет основное колдунство 
  
</modification>

 

Как и в любом XML-файле все теги должны быть открыты и закрыты после содержания

 

Ну, раз мы уже начали делать модификатор, давайте придумаем что он будет делать и по дороге обратим внимание на то как можно делать в OCMOD а как не надо.

 

Вот прямо сразу вспомнилось что часто просят новички и спрашивают в какой код что нужно вставить чтобы если товар закончился на складе надпись на кнопке "купить" менялась на какую-то. Теперь у них будет возможность сделать это самостоятельно.

 

Сделаем радиокнопочку, которая будет включать и выключать наше творение, а также надпись на какую собственно будет заменяться кнопка "купить", обычную, не мультиязычную, когда научитесь писать модификаторы-  научитесь брать готовые части кода, благо в опенкарте уже есть все примеры)

 

 Делать будем на последней версии OcStore 2.3.0.2.4

 

Структура операции в OCMOD файле очень простая

 

<file path="Путь к файлу"> 
	<operation error="действие при ошибке">
		<search><![CDATA[что ищем]]></search>
		<add position="операция"><![CDATA[ 
			что вставляем или меняем
		]]></add>
	</operation>
</file>

 

 

Путь к файлу, который мы будем модифицировать

 

Можно написать несколько путей через |

<file path="catalog/controller/common/home.php|catalog/controller/common/column_left.php"> 

 

В пути можно использовать звездочки и скобочки

* - это любой символ в пути

{} - это набор файлов, подробнее опишу дальше

 

Сейчас мы делаем админку для нашего модификатора и нам понадобятся файлы

 

admin/controller/setting/setting.php

admin/view/template/setting/setting.tpl

 

 

т.е. операция примет вид

<file path="admin/view/template/setting/setting.tpl"> 
	<operation error="skip">
		<search><![CDATA[<label class="col-sm-2 control-label" for="input-admin-limit"><span data-toggle="tooltip" title="<?php echo $help_limit_admin; ?>"><?php echo $entry_limit_admin; ?></span></label>]]></search>
		<add position="before"><![CDATA[ 
			
		]]></add>
	</operation>
</file>

 

действие при ошибке - необязательно, но я предпочитаю писать skip - просто пройти дальше мимо

можно писать abort (не надо, это оборвет исполнение всей цепочки) или log (писать в лог, но вроде и так все пишется)

 

Нам нужна радиокнопка которая вкл-выкл действие и сама надпись для этой кнопки

Откроем файл шаблона настроек магазина admin/view/template/setting/setting.tpl и найдем похожий кусочек с радиокнопкой на вкладке "Опции", а заодно и текстовое поле

image.png.c7fdecec3e973b1243f9b763af6ffae0.png

 

 

Откроем консоль по ф12 и посмотрим как называется этот элемент и заодно соседний

 

 

Спойлер

image.thumb.png.9ec1b34db2037c5e718bb9be1690377e.png

 

 

Окей, найдем в tpl-файле этот кусочек кода (для простоты перед ним и будем вставлять наши настройки)

 

Спойлер

image.thumb.png.db2a4aa7ddc7979ef865a987c812b6d0.png

 

Теперь подумаем куда нам прицепиться.

Самое главное для OCMOD файла - найти УНИКАЛЬНЫЙ ЭЛЕМЕНТ к которому мы будем привязываться, не к <?php echo $text_yes; ?>, не к <div class="form-group required"> а к чему-то что с малой вероятностью будет изменено коллегами-конкурентами-вашими программистами

 

Просто запомните, перед тем как идти дальше, проверьте что то что вы указываете в элементе search встречается один раз и ровно там где вам нужно, если нет - ищите дальше куда можно влезть чтобы вас потом не проклинали.

 

На этом примере мы можем прицепиться к 

<legend><?php echo $text_product; ?></legend>

 

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

 

Смотрим, нам нужно вклиниться вот сюда

Спойлер

 image.thumb.png.7e8f1cab94ae0de0043d72c557fdf092.png 

 

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

я вижу уникальную конструкцию это название блока "кол-во элементов в админке" - туда и пойдем

 

Берем всю строчку и указываем что ее нужно искать (надо вставлять без переносов и пробелов в начале и в конце

 

<search><![CDATA[<label class="col-sm-2 control-label" for="input-admin-limit"><span data-toggle="tooltip" title="<?php echo $help_limit_admin; ?>"><?php echo $entry_limit_admin; ?></span></label>]]></search>

 

search понимает параметр index , то есть если написать <search index="3"><![CDATA[</label>]]></search> то наш код будет исполняться около 4(!) вхождения </label> на странице - первый элемент это index="0". Старайтесь не использовать это без особой необходимости, кто-то вставит раньше похожий кусочек и все сломается, ваша задача максимально оградить себя от внешних влияний. 

 

Без параметра index - операция применится ко всем вхождениям искомой строки в файле

 

Можно искать по части строки, но старайтесь по целой

 

Еще search понимает атрибут trim, но обычно это не применяется на практике

 

Теперь будем наконец-то вставлять код.

 

Берем просто копипастим радиокнопку вместе с текстовым полем и переименовываем переменные в 1 - то что нам надо, 2 - чтобы тот кто откроет после вас понял что имеется ввиду

 

Получается что-то такое

Спойлер








           <div class="form-group">
             <label class="col-sm-2 control-label"><span data-toggle="tooltip" title="<?php echo $help_replace_cart_button; ?>"><?php echo $entry_replace_cart_button; ?></span></label>
             <div class="col-sm-10">
               <label class="radio-inline">
                 <?php if ($config_replace_cart_button) { ?>
                 <input type="radio" name="config_replace_cart_button" value="1" checked="checked" />
                 <?php echo $text_yes; ?>
                 <?php } else { ?>
                 <input type="radio" name="config_replace_cart_button" value="1" />
                 <?php echo $text_yes; ?>
                 <?php } ?>
               </label>
               <label class="radio-inline">
                 <?php if (!$config_replace_cart_button) { ?>
                 <input type="radio" name="config_replace_cart_button" value="0" checked="checked" />
                 <?php echo $text_no; ?>
                 <?php } else { ?>
                 <input type="radio" name="config_replace_cart_button" value="0" />
                 <?php echo $text_no; ?>
                 <?php } ?>
               </label>
             </div>
           </div>
           <div class="form-group">
             <label class="col-sm-2 control-label" for="input-replace-cart-button-text"><span data-toggle="tooltip" title="<?php echo $help_replace_cart_button_text; ?>"><?php echo $entry_replace_cart_button_text; ?></span></label>
             <div class="col-sm-10">
               <input type="text" name="config_replace_cart_button_text" value="<?php echo $config_replace_cart_button_text; ?>" placeholder="<?php echo $entry_replace_cart_button_text; ?>" id="input-replace-cart-button-text" class="form-control" />
             </div>
           </div>			

 

 

Теперь нам нужно вставить это перед блоком, но там div с классом form-group

 

используем before offset="1" - это значит что операция начнет применяться на 1 строку выше той которую мы ищем

точно так же работает after - это вставка после искомой строки

replace - заменяет искомую строку на то что мы напишем

 

несколько строк одновременно в одной операции поиска искать нельзя!

 

У нас получится такая операция и с этим файлом мы закончили

Спойлер








	<operation error="skip">
		<search><![CDATA[<label class="col-sm-2 control-label" for="input-admin-limit"><span data-toggle="tooltip" title="<?php echo $help_limit_admin; ?>"><?php echo $entry_limit_admin; ?></span></label>]]></search>
		<add position="before" offset="1"><![CDATA[ 
           <div class="form-group">
             <label class="col-sm-2 control-label"><span data-toggle="tooltip" title="<?php echo $help_replace_cart_button; ?>"><?php echo $entry_replace_cart_button; ?></span></label>
             <div class="col-sm-10">
               <label class="radio-inline">
                 <?php if ($config_replace_cart_button) { ?>
                 <input type="radio" name="config_replace_cart_button" value="1" checked="checked" />
                 <?php echo $text_yes; ?>
                 <?php } else { ?>
                 <input type="radio" name="config_replace_cart_button" value="1" />
                 <?php echo $text_yes; ?>
                 <?php } ?>
               </label>
               <label class="radio-inline">
                 <?php if (!$config_replace_cart_button) { ?>
                 <input type="radio" name="config_replace_cart_button" value="0" checked="checked" />
                 <?php echo $text_no; ?>
                 <?php } else { ?>
                 <input type="radio" name="config_replace_cart_button" value="0" />
                 <?php echo $text_no; ?>
                 <?php } ?>
               </label>
             </div>
           </div>
           <div class="form-group">
             <label class="col-sm-2 control-label" for="input-replace-cart-button-text"><span data-toggle="tooltip" title="<?php echo $help_replace_cart_button_text; ?>"><?php echo $entry_replace_cart_button_text; ?></span></label>
             <div class="col-sm-10">
               <input type="text" name="config_replace_cart_button_text" value="<?php echo $config_replace_cart_button_text; ?>" placeholder="<?php echo $entry_replace_cart_button_text; ?>" id="input-replace-cart-button-text" class="form-control" />
             </div>
           </div>			
		]]></add>
	</operation>

 

 

теперь нужно вдохнуть жизнь в переменные

У нас здесь 2 переменные настроек

это $config_replace_cart_button и $config_replace_cart_button_text а также языковые переменные

 

открываем 

admin/controller/setting/setting.php

 

и ищем там 2 места

где добавляются языковые переменные 

image.thumb.png.499b49786ef5a2c88faea993b9ada583.png

 

и непосредственно сохраняются настройки, ищем config_product_count

 

image.thumb.png.8977522258e4eea69b43bf08cf3cc6cd.png

 

у нас будет 2 операции (можно в одной, но лучше текстовые переменные туда где текстовые, а настройки к настройкам, чтобы выглядело "как родное"

Точно так же копипастим код, переименовываем переменные и получаем что-то такое

 

Спойлер








<file path="admin/controller/setting/setting.php"> 
	<operation error="skip">
		<search><![CDATA[$data['entry_status'] = $this->language->get('entry_status');]]></search>
		<add position="after"><![CDATA[ 
           $data['entry_replace_cart_button'] = $this->language->get('entry_replace_cart_button');
           $data['help_replace_cart_button'] = $this->language->get('help_replace_cart_button');
           $data['entry_replace_cart_button_text'] = $this->language->get('entry_replace_cart_button_text');
           $data['help_replace_cart_button_text'] = $this->language->get('help_replace_cart_button_text');
		]]></add>
	</operation>
	<operation error="skip">
		<search><![CDATA[if (isset($this->request->post['config_product_count'])) {]]></search>
		<add position="before"><![CDATA[ 
		
		if (isset($this->request->post['config_replace_cart_button'])) {
			$data['config_replace_cart_button'] = $this->request->post['config_replace_cart_button'];
		} else {
			$data['config_replace_cart_button'] = $this->config->get('config_replace_cart_button');
		}
		if (isset($this->request->post['config_replace_cart_button_text'])) {
			$data['config_replace_cart_button_text'] = $this->request->post['config_replace_cart_button_text'];
		} else {
			$data['config_replace_cart_button_text'] = $this->config->get('config_replace_cart_button_text');
		}

		]]></add>
	</operation>
</file>

 

 

Здесь offset нам не нужен, просто вставляем до и после

 

Всегда проверяйте что то, к чему привязываемся - уникально!

 

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

 

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

<file path="admin/language/*/setting/setting.php"> 

 

Это значит что наш модификатор пробежится по всем папкам в admin/language и поищет в каждой файл setting/setting.php

Можно написать так <file path="admin/language/*/*/set*.php"> или так <file path="admin/*/*/*/setting.php">

Если нужны конкретные языки, давайте попробуем ру и англ и увидим как работают скобочки

 

<file path="admin/language/{ru-ru,en-gb}/setting/setting.php"> 

 

это явное указание нескольких папок-файлов в пути

 

в них обоих есть // Text поэтому не будем мудрствовать лукаво

Спойлер

 









<file path="admin/language/{ru-ru,en-gb}/setting/setting.php"> 
	<operation error="skip">
		<search><![CDATA[// Text]]></search>
		<add position="after"><![CDATA[ 
		$_['entry_replace_cart_button']        = 'Заменять кнопку "купить" при нулевом остатке';
		$_['help_replace_cart_button']         = 'Если включить - будет работать замена';
		$_['entry_replace_cart_button_text']   = 'Текст на кнопке купить при количестве 0';
		$_['help_replace_cart_button_text']    = 'Будет отображаться на кнопке при нулевом количестве';
		]]></add>
	</operation>
</file>

 

 

И, о чудо, админку для модуля мы написали и она даже работает!

 

image.png.8178aa86666ab37dcf06332d1174e75b.png

 

 

Теперь будем делать самое главное - чтобы это все работало

 

Начнем с товара, это контроллер product/product и шаблон по такому же пути

 

В контроллере нам нужно получить статус нашей модификации и текст для кнопки (а еще количество товара на склада)

 

Получаем

 

Спойлер








<file path="catalog/controller/product/product.php"> 
	<operation error="skip">
		<search><![CDATA[$data['points'] = $product_info['points'];]]></search>
		<add position="after"><![CDATA[ 
		$data['quantity'] = $product_info['quantity'];
		$data['replace_cart_button_status'] = $this->config->get('config_replace_cart_button');
		$data['replace_cart_button_text'] = $this->config->get('config_replace_cart_button_text');
		]]></add>
	</operation>
</file>

 

 

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

 

$data['replace_cart_button_status'] = $this->config->get('config_replace_cart_button') && $product_info['quantity'] <= 0 ;

 

Все, все данные у нас уже есть, теперь нужно сделать чтобы магия работала в шаблоне

Будем считать что мы нашли уникальный элемент во всех шаблонах и используем путь

 

catalog/view/theme/*/template/product/product.tpl

 

Поищем кнопку купить

 

<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>

 

Пробуем <?php echo $button_cart; ?> , не подходит, оно используется еще в рекомендуемых товарах и если изменится кнопка в товаре - на всех рекомендуемых получим "под заказ"

 

 

Заменим все целиком и там где название просто выведем нужный текст в зависимости от наших условий, лучше использовать короткий if чтобы оно и смотрелось нормально и не нагромождать if else и тп в и без того длинной строчке

 

Спойлер








<file path="catalog/view/theme/*/template/product/product.tpl"> 
	<operation error="skip">
		<search><![CDATA[<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>]]></search>
		<add position="replace"><![CDATA[<button type="button" id="button-cart" data-loading-text="<?php echo $text_loading; ?>" class="btn btn-primary btn-lg btn-block"><?php echo $replace_cart_button_status ? $replace_cart_button_text : $button_cart; ?></button>]]></add>
	</operation>
</file>

 

 

обратите внимание - replace - тупо заменяет искомое на требуемое, поэтому я рекомендую если вы меняете что-то в одной строке или ее части, так тоже можно - смотрите чтобы оно было без пробелов и переносов, т.к. поломаете верстку и вас никто не будет любить. Также с большой осторожностью используйте offset в replace - он заменяет нижние строки полностью, потренируйтесь на каком-то простом файле

 

Вуаля. Опять работает когда количество 0

 

image.png.b71ba992b0ceef35f98b6128dc7ded03.png

 

 

Ну, мы уже опытные модулеписатели. Айда провернем то же самое в категориях

А заодно на страничке товаров производителя, поиске и акциях, т.к. контроллеры и шаблоны у них практически идентичны

А еще заодно в рекомендуемых товарах на страничке самого товара

 

<file path="catalog/controller/product/*.php">	

 

это значит мы будем искать во всех контроллерах в папке product

можно и так

 

<file path="catalog/controller/product/{category,manufacturer,search,special,product}.php">	

 

Напомню, нам нужно получить статус замены текста на кнопке и, собственно, сам текст

 

Итого 5 контроллеров, ищем строки которые встречаются во всех

 

Возьмем к примеру 

'name'        => $result['name'],

это название товара, встречается везде, навредить мы не сможем

 

<file path="catalog/controller/product/*.php"> 
	<operation error="skip">
		<search><![CDATA['name'        => $result['name'],]]></search>
		<add position="after"><![CDATA[ 
		'replace_cart_button_status'        => $this->config->get('config_replace_cart_button') && $result['quantity'] <= 0,
		'replace_cart_button_text'          => $this->config->get('config_replace_cart_button_text'),
		]]></add>
	</operation>
</file>

 

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

 

Точно так же ищем кнопку "купить" и заменяем ее на похожую конструкцию за исключением того что у нас будет не просто $replace_cart_button_status а $product['replace_cart_button_status']. В нормальных шаблонах эти места одинаковые, поэтому будем считать что у нас идеальные условия.

 

<file path="catalog/view/theme/*/template/product/*.tpl"> 
	<operation error="skip">
		<search><![CDATA[<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>]]></search>
		<add position="replace"><![CDATA[<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 $product['replace_cart_button_status'] ? $product['replace_cart_button_text'] : $button_cart; ?></span></button>]]></add>
	</operation>
</file>

 

вуаля

image.png.a20bd61734a5b450e7bce00654d282d7.png

 

 

Упс, в карточке товара рекомендуемые используют чуть другой код (разницы в 1 символе хватит чтобы мод не сработал), ничего, мы добавим аналогичную операцию к product.tpl

 

	<operation error="skip">
		<search><![CDATA[<button type="button" onclick="cart.add('<?php echo $product['product_id']; ?>', '<?php echo $product['minimum']; ?>');"><span class="hidden-xs hidden-sm hidden-md"><?php echo $button_cart; ?></span> <i class="fa fa-shopping-cart"></i></button>]]></search>
		<add position="replace"><![CDATA[<button type="button" onclick="cart.add('<?php echo $product['product_id']; ?>', '<?php echo $product['minimum']; ?>');"><span class="hidden-xs hidden-sm hidden-md"><?php echo $product['replace_cart_button_status'] ? $product['replace_cart_button_text'] : $button_cart; ?></span> <i class="fa fa-shopping-cart"></i></button>]]></add>
	</operation>

 

 

Как-то это сильно просто

Давайте добавим то же самое еще и в модули

В опенкарте 4 стандартных дефолтных модуля (последние, рекомендуемые, хиты продаж и акции), проделываем с ними то же самое

 

О, ухты! В модулях используется для названия товара то же самое

'name'        => $result['name'],

Сделаем по-умному, изменим путь контроллера там где делали в категориях на

<file path="catalog/controller/{extension/module,product}/*.php"> 

 

и теперь модификатор поищет по обоим путям и добавит переменные везде где нам нужно

 

С шаблоном такое не прокатило, для модулей делаем отдельно

 

<file path="catalog/view/theme/*/template/extension/module/*.tpl"> 
	<operation error="skip">
		<search><![CDATA[<button type="button" onclick="cart.add('<?php echo $product['product_id']; ?>');"><i class="fa fa-shopping-cart"></i> <span class="hidden-xs hidden-sm hidden-md"><?php echo $button_cart; ?></span></button>]]></search>
		<add position="replace"><![CDATA[<button type="button" onclick="cart.add('<?php echo $product['product_id']; ?>');"><i class="fa fa-shopping-cart"></i> <span class="hidden-xs hidden-sm hidden-md"><?php echo $product['replace_cart_button_status'] ? $product['replace_cart_button_text'] : $button_cart; ?></span></button>]]></add>
	</operation>
</file>

 

 

Все сделали и ой

image.thumb.png.bba6abe57f6a6ae2acc86059a373316a.png

 

Почему-то это еще с версии 1.5 живет и никто не осмеливается это менять

в контроллере рекомендуемых - не $result а $product_info

Делаем исключение и добавляем туда отдельно

 

<file path="catalog/controller/extension/module/featured.php"> 
	<operation error="skip">
		<search><![CDATA['name'        => $product_info['name'],]]></search>
		<add position="after"><![CDATA[ 
		'replace_cart_button_status'        => $this->config->get('config_replace_cart_button') && $product_info['quantity'] <= 0,
		'replace_cart_button_text'          => $this->config->get('config_replace_cart_button_text'),
		]]></add>
	</operation>
</file>   

 

Все работает, и это было совсем не больно

 

И вот у нас уже готовый модификатор который немного изменив под свои хотелки можно продать за 300р))

 

super_mod.ocmod.xml

 

 

Итого краткое резюме:

- Всегда проверяйте свое условие search чтобы оно было уникальным и никому не мешало, не привязывайтесь к $category_info или $data['heading_title']

- Используйте offset осторожно, а в replace вообще не используйте

- Старайтесь использовать меньшее количество кода, но оставляйте его читаемым

- есть еще search regex но это совсем другая история :)

 

Если что-то сломалось после применения модификатора из папки system нужно всего лишь переименовать его, скажем, в .ocmod.xml_ , т.е. изменить расширение и обновить кеш модификаторов

Если сломалась страница обновления модификаторов - нужно очистить папку storage/modification (путь к ней можно подглядеть в config.php) тогда, страничка откроется

Это работает если ничего не правилось в кеше модификаторов - но  у кого так - тот и сам знает все боли и их не обновляет :)

 

 

Это все основано на моем опыте и является моим личным мнением и видением методики написания модификаторов, если у вас есть советы-пожелания - добро пожаловать в комменты

 

 

Спасибо за внимание, ваш spectre

 

 

image.png

image.png

  • +1 27

56 коментарів


Recommended Comments



Цитата

Итак, OCMOD-модификатор это простой XML-файл, который изменяет PHP-файлы и/или tpl/twig- файлы шаблонов.

Неправда. Это только частный случай, а так-то модфикатор .ocmod.zip может из много чего состоять.
 

<name>Name Of Mega Modification - название нашего супер модуля</name>
<code>name_of_mega_modification - внутренний код модификатора</code>
<version>1.0 2.3.x-3.0.x - можно написать версию файла, для каких версий подходит, ну так, чтоб понятно было</version>
<author>spectre - ваш супер ник</author>
<link>https://freelancer.od.ua/ - ваш суперсайт</link>

Надо сказать, что здесь все фигня неважно и ни на что(кроме внешнего оформления) не влияет, кроме code. code должен быть уникальным, иначе при установке получите ошибку.
 

Цитата

действие при ошибке - необязательно, но я предпочитаю писать skip

Отметьте, что ошибка здесь - это ошибка поиска вхождения образца, а не ошибка выполнения скрипта. Если найти не удалось, то по "skip" ищем следующее, а по "abort" прекращаем обрабатывать модификатор (но то, что им уже модфицировано, обратно не откатывается и так и остается в магазине).
 

Цитата

Берем всю строчку и указываем что ее нужно искать (надо вставлять без переносов и пробелов в начале и в конце

А самого главного так и не сказали - мультистрочный поиск не работает
 

Цитата

Всегда проверяйте что то, к чему привязываемся - уникально!


А вот и не всегда это хорошо (хотя чаще всего стоит стремиться). Иногда проще привязаться к не уникальным строкам, если они гарантированно будут и при этом "коллеги-программисты"  на них никогда(почти) не покушаются 
 

Цитата

<search><![CDATA[// Text]]></search>

А вот это плохо. Не используйте комменты для поиска без крайней необходимости. Все знают, что комменты - это "ничто", с ними можно что угодно делать, менять, удалять. И тут вы вдруг привязываете к комментарию код... Еще и других учите этой преступной методике :)

  • +1 2
Надіслати

@Shureg, спасибо за комент

 

действительно, весь модификатор может состоять из больше чем xml-файла, но это настолько костыли что я никогда их не рассматривал и это огромнейшая дыра в безопасности опенкарт, имея доступ к админке можно запустить php-скрипт, закачать шелл куда угодно, sql ладно, его и через бекап рестор можно грузануть, но 90% бед, связанных с вирусней у опенкарта связаны именно с этим функционалом, файлы проще и надежнее

 

abort прекращает всю цепочку выполнения, будет обидно если что-то не сработает дальше вашего модификатора 

 

мультистрочный поиск да, ценное замечание, но кому это может придти в голову не знаю) 

 

неуникальные строки да, имеют место быть но при необходимости 

покажите пример где по-вашему жизненно необходимо, я крайне редко такое встречаю 

 

про комменты - как бы вы сделали привязку к языковому файлу setting/setting  чтобы применить мод ко всем языкам? ну и что плохого привязаться к комменту кто онлайн в контроллере футера к примеру? кто его будет трогать? 

 

цель поста она больше объяснить как оно работает чтобы не делать откровенной херни типа индекс 35 и реплейс оффсет 36, или заменять все вхождения getProducts в контроллере как делают некоторые авторы популярных фильтров, и думать о других, а не навязать какую-то "технику" написания, у каждого она вырабатывается со временем своя 

Надіслати

- пишите модификаторы - думая, что кто-то тоже может повторить ваши действия и что тогда выйдет.

- не делайте массовую установку кода во все файлы, лучше подробнее укажите пути.

- старайтесь привязываться к названиям функций, массивам, например:

Спойлер





        <operation error="skip">
            <search index="0">
            <![CDATA[function index(]]>
            </search>
            <add position="after">
            <![CDATA[			$data['mymodule'] = 'mymodule';]]>
            </add>
        </operation>
        <operation error="skip">
            <search index="0">
            <![CDATA[foreach ($products as $product) {]]>
            </search>
            <add position="before">
            <![CDATA[				$data['mymodule'] = 'mymodule';]]>
            </add>
        </operation>
        <operation error="skip">
            <search index="0">
            <![CDATA[$data['products'][] = array(]]>
            </search>
            <add position="after">
            <![CDATA[					'mymodule' => 'mymodule';]]>
            </add>
        </operation>
        <operation error="skip">
            <search index="0">
            <![CDATA[?php]]>
            </search>
            <add position="after">
            <![CDATA[$_['mymodule'] = 'mymodule';]]>
            </add>
        </operation>

 

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

  • +1 1
Надіслати

Если вы добавляете, например языковые переменные и свой функционал, то попробуйте его объединить в один блок

пример
 

        <operation error="skip">
            <search index="0">
            <![CDATA[function index(]]>
            </search>
            <add position="after">
            <![CDATA[			$data['mymodule'] = 'mymodule';]]>
            </add>
        </operation>
        <operation error="skip">
            <search index="0">
            <![CDATA[$data['price']]>
            </search>
            <add position="after">
            <![CDATA[			$data['myprice'] = 'myprice';
			]]>
            </add>
        </operation>

делаем так
 

        <operation error="skip">
            <search index="0">
            <![CDATA[$data['price']]>
            </search>
            <add position="after">
            <![CDATA[			$data['myprice'] = 'myprice';
						$data['mymodule'] = 'mymodule';
			
			]]>
            </add>
        </operation>




Не привязывайтесь к значениям  языковых переменных
 

        <operation error="skip">
            <search index="0">
            <![CDATA[$_['text']     = 'чей-то текст';]]>
            </search>
            <add position="after">
            <![CDATA[$_['text_my']     = 'мой текст';

			
			]]>
            </add>
        </operation>

Делаем
        <operation error="skip">
            <search index="0">
            <![CDATA[$_['text']]]>
            </search>
            <add position="after">
            <![CDATA[$_['text_my']     = 'мой текст';

			
			]]>
            </add>
        </operation>

 

  • +1 1
Надіслати
2 часа назад, spectre сказал:

про комменты - как бы вы сделали привязку к языковому файлу setting/setting  чтобы применить мод ко всем языкам? ну и что плохого привязаться к комменту кто онлайн в контроллере футера к примеру? кто его будет трогать? 

 

Вот так:

<search index="0"><![CDATA[php]]></search>
<add position="after"><![CDATA[

Комменты трогать не надо. "Изменение и удаление любых комментариев не должно влиять на исполнение кода" - просто считать это абсолютным правилом.

И да, кстати, вы не отметили еще два момента - искать можно не по целой строке. И даже менять часть строки. 
А еще, без индекса будут заменяться все вхождения строки поиска. Так что иногда его как раз стоит ставить, на всякий случай.
 

2 часа назад, spectre сказал:

мультистрочный поиск да, ценное замечание, но кому это может придти в голову не знаю) 

Вообще-то очень полезная вещь была бы. Приходит в голову любому, кто начинает работать с ocmod. Жалко, что этого нет :cry: (есть разные допилы, чтобы был мультистрочный поиск, но это уже только для себя)

Надіслати
12 минут назад, stickpro сказал:

в новых версия OC ocmod выпиливают, учимся писать ocmod))

Очень трудно продавать товары  в скрипте будущего. Приходится торговать на том, который уже есть. А в нем ocmod  в наличии

Надіслати

В тройке еще версия важна, если загружать с такой - будет обновление, с другой - будет добавлен новый модификатор.

  • +1 2
Надіслати
7 минут назад, Shureg сказал:

<search index="0"><![CDATA[php]]></search> <add position="after"><![CDATA[

 

<?

текст

$_['config_php'] 

 

это уже вопрос подхода, опыта и прочего

цепляться к <?php тоже не очень, сломается подсветка, вариантов много

а вот что плохого в том чтобы цепляться к комментариям, которые есть во всех версиях опенкарт и они идентичны я так и не понял

 

9 минут назад, Shureg сказал:

вы не отметили еще два момента - искать можно не по целой строке. И даже менять часть строки. 

 

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

  • +1 1
Надіслати
11 минут назад, spectre сказал:

цепляться к <?php тоже не очень, сломается подсветка, вариантов много

У меня нигде ничего не ломается:
 

<file path="admin/language/{ru-ru,en-gb}/setting/setting.php">
	<operation error="skip">
		<search index="0"><![CDATA[php]]></search>
		<add position="after"><![CDATA[
		$_['entry_replace_cart_button']        = 'Заменять кнопку "купить" при нулевом остатке';
		$_['help_replace_cart_button']         = 'Если включить - будет работать замена';
		$_['entry_replace_cart_button_text']   = 'Текст на кнопке купить при количестве 0';
		$_['help_replace_cart_button_text']    = 'Будет отображаться на кнопке при нулевом количестве';
		]]></add>
	</operation>
</file>
<?php

		$_['entry_replace_cart_button']        = 'Заменять кнопку "купить" при нулевом остатке';
		$_['help_replace_cart_button']         = 'Если включить - будет работать замена';
		$_['entry_replace_cart_button_text']   = 'Текст на кнопке купить при количестве 0';
		$_['help_replace_cart_button_text']    = 'Будет отображаться на кнопке при нулевом количестве';
		
// Heading
$_['heading_title']                = 'Settings';

 

12 минут назад, spectre сказал:

а вот что плохого в том чтобы цепляться к комментариям, которые есть во всех версиях опенкарт и они идентичны я так и не понял

Плохое в том, что любой может изменить этот коммент совершенно произвольно. Удалить.  Или где-нибудь добавить свой точно такой же. И при этом он будет совершенно прав со всех точек зрения, а вот разработчик, который привязал в своей работе что-то к комментарию - наоборот, полностью не прав. И тоже со всех точек зрения. 
В данном случае это тем более не оправдано, что вариантов привязки сколько угодно. Хоть просто к php, хоть к $_['heading_title'] или $_['text_error'], они тоже никуда не денутся 

Надіслати

Вот про search regex еще было бы классно расписать, на самом деле с одной стороны очень удобный инструмент, а с другой стороны при неосторожном использовании можно дров наломать прям по-тяжелому.

Надіслати
1 час назад, AlexDW сказал:

всякие нюансы до кучи

 

Оооо прикольно! А какие там еще условия можно добавлять? есть там что-то типа простого if?

Надіслати
28 минут назад, OtezVikentiy сказал:

Оооо прикольно! А какие там еще условия можно добавлять? есть там что-то типа простого if?

откройте метод refresh

Надіслати

Спасибо за статью, думаю многим начинающим будет полезной. 
Дополню от себя:

1. index, replace, offset, regex я стараюсь вообще не использовать без ситуации, когда без этого вообще никак и даже после этого 2 раза подумаю прежде чем использовать. Иначе потом куча головной боли прежде всего для самого разработчика по поддержке так как чем больше подобных конструкций тем больше вероятность конфликта. 

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

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

4. не нужно в модификатор писать кучу кода. Если есть какой-то большой кусок кода, который нужно выполнить то намного лучше вынести его в отдельный файл, например модель и через модификатор добавить 2 строчки кода: в первой подключить эту модель и во второй получить результат выполнения метода. Чем добавлять в контроллер 50 строк кода.

5. по возможности избегать использование модификаторов, а там где без этого никак - уменьшать их по максимуму. 

  • +1 4
Надіслати
Цитата

Первый вариант предпочтительнее, т.к. его легче править, прямо фтп-клиентом и можно уже обновлять кеш

Для разработчика - да, но неоднократно сталкивался с ситуациями, когда у пользователя "ложится" сайт из-за непонимания, как отключать установленные таким образом модификаторы. Обычно это возникает после установки модуля/шаблона, в состав которых входит какой-нибудь модификатор, который вместе со всеми остальными файлами копируется на хостинг (сам модификатор, соответственно, в /system), а когда пользователь решает сменить шаблон или удалить/отключить модуль, то его ждет сюрприз в виде или кучи предупреждений и ошибок, или просто 500-й с белым экраном. Подавляющее большинство разработчиков, которые практикуют запись модификаторов в /system, почему-то не пишут инструкцию по корректному удалению/отключению своих дополнений.

Надіслати
Цитата

иногда install.sql - то же самое, только для запросов в бд

Штука с последствиями. Там же не вписать DB_PREFIX, а не все при установке оставляют префикс по умолчанию oc_ для таблиц в базе.

Лучше писать запросы в install.php или вызывать метод модели модуля в install() контроллера модуля. В любом случае все запросы должны учитывать DB_PREFIX конкретного сайта.

 

---

Цитата

файл install.php - php-файл который выполняется во время установки модификатора

В тройке он не работает. Но в двойке - в него как раз можно вписать, чтобы при повторной установке модуля его предыдущий модификатор сам бы сносился без дополнительных действий со стороны пользователя.

 

 

 

  • +1 1
Надіслати
10 минут назад, SergeTkach сказал:

Штука с последствиями. Там же не вписать DB_PREFIX, а не все при установке оставляют префикс по умолчанию oc_ для таблиц в базе.

Лучше писать запросы в install.php или вызывать метод модели модуля в install() контроллера модуля. В любом случае все запросы должны учитывать DB_PREFIX конкретного сайта.

неправда, если посмотрите в код

 

if (preg_match('/;\s*$/', $line)) {
								$sql = str_replace(" `oc_", " `" . DB_PREFIX, $sql);

								$this->db->query($sql);

								$sql = '';
							}



Но это уже давно забы(и)тое

  • +1 1
Надіслати

И еще, здорово взять и дополнить свой комментарий к уже существующей статье. А вот чтобы написать статью с нуля, надо потратить несколько часов. @dinox, дайте  @spectre еще один значок "Блогера". Имхо, первый полагается за вот это.

 

  • +1 1
Надіслати
14 минут назад, chukcha сказал:

неправда, если посмотрите в код

 




if (preg_match('/;\s*$/', $line)) {
								$sql = str_replace(" `oc_", " `" . DB_PREFIX, $sql);

								$this->db->query($sql);

								$sql = '';
							}



Но это уже давно забы(и)тое

Хм... Надо будет попробовать. Уже, честно говоря и не помню, когда это было и при каких обстоятельстах, но были проблемы с oc_

В тройке не нахожу этого кода. А в двойке, по идее, должно работать. Но стереотип у меня сложился еще до использования тройки... Хм...

 

Надіслати
12 минут назад, ArtemPitov сказал:

<search><![CDATA[// Text]]></search>

 

Лучше искать <?php он 100% будет

 


<search><![CDATA[<?php]]></search>

 

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

у каждого вырабатывается свой best practice со временем 

  • +1 1
Надіслати
26 минут назад, spectre сказал:

у каждого вырабатывается свой best practice со временем 

Это bad practice. Лучше бы вы привязывались к $_['text_stores']  , например. Стандартная переменная ос явно стабильней какого-то комментария. Использование комментов для поиска - это поперек и практики, и идеологии.

Надіслати
8 часов назад, Shureg сказал:

поперек и практики, и идеологии.

 

чьей идеологии? 

этот комментарий есть ещё в версии 1.4 и его никто не трогал никогда , он как памятник) отчего ж нельзя его использовать для поиска 

 

лучше давайте какие-то не очевидные и прикольные штуки разберём 

 

 

  • +1 2
Надіслати

Створіть аккаунт або увійдіть для коментування

Ви повинні бути користувачем, щоб залишити коментар

Створити обліковий запис

Зареєструйтеся для отримання облікового запису. Це просто!

Зареєструвати аккаунт

Вхід

Уже зареєстровані? Увійдіть тут.

Вхід зараз
  • Зараз на сторінці   0 користувачів

    • Ні користувачів, які переглядиють цю сторінку

×
×
  • Створити...

Important Information

На нашому сайті використовуються файли cookie і відбувається обробка деяких персональних даних користувачів, щоб поліпшити користувальницький інтерфейс. Щоб дізнатися для чого і які персональні дані ми обробляємо перейдіть за посиланням . Якщо Ви натиснете «Я даю згоду», це означає, що Ви розумієте і приймаєте всі умови, зазначені в цьому Повідомленні про конфіденційність.