Вывод всех опций minishop2 и автоматическое их добавление в словарь одной кнопкой.

Всем привет, как всегда возникла задача максимально упростить и автоматизировать рутинные процессы в MODX. Будем делать автоматическое добавление опций в словарь и автоматически подтягивать список опций, куда вам угодно будет.Если у вас есть советы как улучшить мой вариант или указать на ошибки — буду только рад. Честно признаюсь, что не искал похожие варианты, так что если повторю чей-то функционал — извиняйте :)

Получаем список всех опций minishop2


Тут все очень просто, создаем сниппет getAllOptions и вставляем в него этот код:

$optCollection = $modx->getCollection('msOption');
$options = '';
foreach ($optCollection as $item) {
  $options .= $item->get('key').',';
}
return $options;

Далее просто вызываем этот сниппет в нужном месте, в моем случае это сниппет msOptionsPrice.option

{set $options = '!getAllOptions'|snippet}
{'!msOptionsPrice.option'|snippet:[
    'options' => $options,
    'tpl' => '@FILE chunk/product.options.tpl'
]}

В итоге все опции которые будут добавляться, будут приходить в этот сниппет самостоятельно. Точно таким же способом можно подтягивать опции и в mFilter2.

Автоматическое добавление опций в словарь


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

Создаем файл addOptionsToLexicon.php (да, мне так удобно называть), и помещаем его в директорию assets, в моем случае это assets/components/msCustomField/ и вставляем в него код:

<?php
define('MODX_API_MODE', true);
require_once dirname(dirname(dirname(dirname(__FILE__)))).'/index.php';
// Подключаем MODX API
if (!$modx->user->isAuthenticated('mgr')) {
  return false;
}
$modx->getService('error','error.modError');
$modx->setLogLevel(modX::LOG_LEVEL_INFO);
$modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');
if (!$modx->getService('minishop2')) {
  return false;
}
//Проверяем авторизирован ли пользователь
$optCollection = $modx->getCollection('msOption');
// Получаем все опции минишопа
if (!$optCollection){
  return false;
}
foreach ($optCollection as $opt) {
  $key = 'ms2_product_'.$opt->get('key'); // Добавляем к каждой опции префикс
  $q = $modx->newQuery('modLexiconEntry');
  $q->where(array(
    'namespace' => 'minishop2',
    'topic' => 'default',
    'name' => $key
  ));
  $lexicon = $modx->getObject('modLexiconEntry', $q);
  // Пытаемся получить запись из словаря для этой опции
  if (!$lexicon){
    // Если записи нет - добавляем опцию в словарь
    $newLexicon = $modx->newObject('modLexiconEntry', array(
      'name' => $key,
      'value' => $opt->get('caption'),
      'namespace' => 'minishop2',
      'topic' => 'default',
      'language' => 'ru',
      'createdon' => time()
    ));
    $newLexicon->save();
  }else{
    // Иначе просто обновляем у нее значение, на случай, если отредактируют название опции
    $lexicon->set('value', $opt->caption);
    $lexicon->save();
  }
}

Теперь нам нужно его как-то запускать, по ссылке запускать не комильфо, поэтому сделаем кнопку.

Добавляем свою кнопку на страницу опций minishop2


1. Создаем плагин на событие msOnManagerCustomCssJs и вставляем в него код:

<?php
switch ($modx->event->name) {
      case 'msOnManagerCustomCssJs':
          if ($page === 'settings'){
            $modx->controller->addLastJavascript(MODX_ASSETS_URL.'components/msCustomSettings/default.js');
          } 
          return;
      break;
  }

2. Внутри assets/components/msCustomSettings/ создаем файл default.js, открываем его и вставляем этот код:

Ext.override(miniShop2.grid.Option, {
  // Переписываем метод getTopBar  из класса miniShop2.grid.Option
  getTopBar: function (config) {
    return [{
      text: '<i class="icon icon-plus"></i> ' + _('ms2_btn_create'),
      handler: this.createOption,
      scope: this
    }, {
      text: '<i class="icon icon-check"></i> ' + _('ms2_btn_assign'),
      id: config.id + '-btn-assign',
      handler: this.assignOption,
      scope: this,
      disabled: true,
    }, {
      // Добавляем нашу кнопку
      text: '<i class="icon icon-plus"></i> ' + 'Записать в словарь',
      handler: function () {
        // Вызываем при клике Ajax запрос на наш файл с логикой 
        Ext.Ajax.request({
          url: '../assets/components/msCustomField/addOptionsToLexicon.php',
          success: function(response, opts) {
            Ext.MessageBox.alert('Успешно','Опции успешно добавлены в словарь');
          },
          failure: function(response, opts) {
            Ext.MessageBox.alert('Ошибка');
          }
        });
      },
      scope: this
    }, '->', this.getSearchField()];
  },
});

И в итоге мы получаем вот такую кнопку на странице опций, в настройках минишопа.



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



Такой вариант защищает глупеньких заказчиков от страшных слов и дает им возможность самостоятельно работать с опциями и связанными с ними компонентами, такими как msOptionsPrice2 или mSearch2(mFilter2).
И больше не нужно добавлять опцию и бежать в словарь, достаточно просто нажать кнопочку :)

Я очень надеюсь, что это будет кому-то полезно, лично я постоянно сталкиваюсь с этим.
Если у вас есть предложения как улучшить код, дополнить его еще какими-то фишками, буду рад почитать.
Написание подобных, простых мануалов помогает мне улучшать свой скил программирования и нести хоть какую-то пользу людям. Так что строго не судите :)
Vlad Brise
14 марта 2019, 19:47
17
619
+14
Поблагодарить автора Отправить деньги

Комментарии: 15

Pavel Zarubin
15 марта 2019, 14:15
+1
Не понятно для чего была необходимость использовать сырые запросы sql? Особенно вот это нравится, фильтрация данных? Зачем? И так сойдет!

$sql = "INSERT INTO modx_lexicon_entries (name, value, topic, namespace, language, createdon) VALUES ('$key', '$opt->caption', 'default', 'minishop2', 'ru', CURRENT_TIMESTAMP)";
Ок, предположим что у нас доступ в админку есть только у админов, но файл то находится в assets, его может запустить кто угодно, проверять права? Зачем! Использовать процессоры где проверка прав уже предусмотрена? В жопу!
Очень грустно что статья висит уже день а об этом только я говорю, еще полтора года назад автора закидали бы ссаными тряпками за такой код и дыру в безопасности
    Vlad Brise
    15 марта 2019, 14:17
    0
    Спасибо за комментарий, исправлю.
Vlad Brise
15 марта 2019, 15:53
+1
Исправил дыры в безопасности, убрал сырые запросы и поставил проверку пользователя, теперь все по человечески. Прошу прощения за говнокод.
    Евгений Webinmd
    15 марта 2019, 19:41
    +1
    if (!$modx->user->isAuthenticated('web')) {
      return false;
    }
    это авторизация в контексте web, а надо в mgr

    Можно еще так проверять (нашёл тут недалеко)

    if ( !$modx->user->hasSessionContext('mgr')   || !$modx->user->isMember('Administrator' )) {
            return false;
        }
      Vlad Brise
      15 марта 2019, 19:46
      0
      Спасибо за подсказку, похоже у меня дефицит внимания разыгрался.
      Я то хотел проверить контекст web, и не заметил что поставил восклицательный знак вначале, сейчас исправлю :)
      Vlad Brise
      15 марта 2019, 19:54
      0
      Исправил, еще раз спасибо, что заметили
Баха Волков
15 марта 2019, 19:56
0
С js тоже не всё так хорошо, дело в том, что ты именно перезаписываешь метод getTopBar а не расширяешь, т.е. в теории если выйдет обновление miniShop2 и в нём этот метод изменится, то люди которые используют предложенное тобой решение не увидят каких-то изменений.

Решение: Получить сначала результат оригинального метода (массив) и добавить в него уже свою кнопку.
    Vlad Brise
    15 марта 2019, 19:58
    0
    через onAvailable?
      Баха Волков
      15 марта 2019, 19:59
      0
      Нет, через прототип
        Vlad Brise
        15 марта 2019, 20:00
        0
        хорошо, спасибо
      Баха Волков
      15 марта 2019, 20:04
      0
      Что-то типа такого

      Ext.override(miniShop2.grid.Option, {
        originalFields: miniShop2.grid.Option.prototype.getTopBar
        ,getTopBar: function (config) {
          var fields = this.originalFields.call(this, config);
      
          fields.push({
              // Твоя кнопка
          })
      
          return fields;
      }
        Vlad Brise
        15 марта 2019, 20:06
        0
        Понял, сейчас попробую, благодарю :)
Баха Волков
15 марта 2019, 20:11
0
Ну и чтобы всё по фэншую было, то по мне лучше использовать MODx.msg.alert вместо Ext.MessageBox.alert
    Vlad Brise
    15 марта 2019, 20:48
    0
    Я не силен в extJs, много чего не знаю, но за советы, спасибо, я то хочу сделать максимально правильно.
Андрей
08 июля 2019, 12:41
0
Добрый день. У меня почему-то пишет опции только в лексиконы minishop2? а можно их записывать еще и в msearch2, а то в фильтре выводятся названия системные.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.