Вывод всех опций 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
modx.pro
17
781
+14
Поблагодарить автора Отправить деньги

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

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, а то в фильтре выводятся названия системные.
                Vlad Brise
                05 сентября 2019, 12:31
                0
                Не пробовал, но думаю можно. Сейчас там неймспейс минишопа, попробуйте адаптировать для mSearch2, думаю проблем не составит :)
                Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                16