epic

epic

С нами с 16 марта 2024; Место в рейтинге пользователей: #1002
vectorserver
03 июля 2025, 11:09
1
+1
Вот тебе решение, тут я использую сниппет phpThumbOn и проверяю файлы которые весят более 1мб и делаю ресайз по ширине 1024px, создай файл в корне сайта msproducts_resizer.php и запусти его, в журнале все сообщения!

<?php
// Ресайзер оригиналов изображений msProduct по быстрому
// Определяем режим API MODX
define('MODX_API_MODE', true);
require 'index.php';

// Инициализация MODX
/** @var modX $modx */
$modx->getService('error', 'error.modError');
$modx->setLogLevel(modX::LOG_LEVEL_FATAL);
$modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');

// Путь к папке с продуктами
$basePath = MODX_BASE_PATH . 'assets/images/products/';

// Размер файла в байтах (1 МБ = 1048576 байт)
$maxFileSize = 1048576;

// Максимальная ширина после ресайза
$maxWidth = 1024;

// Логика поиска и ресайза изображений
function resizeImagesWithPhpThumb($basePath, $maxFileSize, $maxWidth)
{
    global $modx;

    // Получаем список всех подпапок в директории products
    $directories = glob($basePath . '*', GLOB_ONLYDIR);

    foreach ($directories as $dir) {
        // Получаем все файлы изображений в текущей подпапке
        $images = glob($dir . '/*.{jpg,jpeg,png,gif}', GLOB_BRACE);

        foreach ($images as $imagePath) {
            // Проверяем размер файла
            if (filesize($imagePath) > $maxFileSize) {
                try {
                    // Генерируем URL для ресайза с помощью phpThumbOn
                    $relativePath = str_replace(MODX_BASE_PATH, '', $imagePath); // Относительный путь
                    $thumbUrl = $modx->runSnippet('phpThumbOn', [
                        'input' => $relativePath,
                        'options' => "w={$maxWidth}&zc=1", // Ширина 1024px, сохранение пропорций
                    ]);

                    // Путь к новому изображению
                    $newImagePath = MODX_BASE_PATH . ltrim($thumbUrl, '/');

                    // Заменяем оригинальное изображение на новое
                    if (file_exists($newImagePath)) {
                        unlink($imagePath); // Удаляем старое изображение
                        rename($newImagePath, $imagePath); // Переименовываем новое изображение
                    }

                    // Логируем успешное изменение
                    $modx->log(modX::LOG_LEVEL_INFO, "Изображение {$imagePath} успешно изменено.");
                } catch (\Exception $e) {
                    // Логируем ошибку при обработке изображения
                    $modx->log(modX::LOG_LEVEL_ERROR, "Ошибка при обработке изображения {$imagePath}: " . $e->getMessage());
                }
            } else {
                // Логируем, если размер файла меньше порогового значения
                $modx->log(modX::LOG_LEVEL_INFO, "Изображение {$imagePath} меньше 1 МБ, пропускаем.");
            }
        }
    }
}

// Запуск логики
resizeImagesWithPhpThumb($basePath, $maxFileSize, $maxWidth);
Артур Шевченко
29 июля 2022, 00:19
1
+1
Думаю ntt нужно переписать вот эту функцию
// Функция включения моделей
    enableModel: function() {
        // Получаем марку автомобиля
        var marka = this.marka.find(':selected').text().replace(/\(.*?\)$/, '').replace(/\s+$/, '');
        var re = new RegExp('^' + marka);
        // Пробегаем по всем моделям и проверяем имя
        $this.model.find('option').each(function() {
            var $this = $(this);
            // Имя не совпадает - нужно отключить эту модель
            if (!$this.text().match(re) && $this.prop('value') != '') {
                $this.attr('disabled', true);
                $this.hide();
            }
            // В противном случае - включить
            else {
                $this.attr('disabled', false);
                $this.show();
            }
        });
        // И показываем весь блок с моделями
        $this.model.show();
    },
}
тут проверяется есть ли в названии модели марка
if (!$this.text().match(re) && $this.prop('value') != '')
в твоём случае надо выводить каждой модели в дата атрибут название марки и проверять есть ли у текущей опции дата атрибут с такой маркой. Как вариант.
Ivan
06 июня 2022, 14:50
2
0
В общем решение не самое красивое, но рабочее.
1) Копируем файл
/assets/components/msearch2/js/web/default.js
и называем его
/assets/components/msearch2/js/web/custom.js
.
2) В этом файле везде
mse2Config['actionUrl']
заменяем на
'/assets/components/msearch2/actionCustom.php'
3) Копируем файл
/assets/components/msearch2/action.php
и называем его
/assets/components/msearch2/actionCustom.php
4) В html коде сортировки указываем
<a href="#" data-sort="ms|price" data-dir="[[+mse2_sort:is=`ms|price:desc`:then=`desc`]]" data-default="desc" class="sort">По цене <span></span></a>
5) В файле
actionCustom.php
на 76 строчке меняем
$paginatorProperties['sortby'] = !empty($sort)
            ? $mSearch2->getSortFields($sort)
            : '';
на
$sorting = '';
            if (!empty($sort)) {
            $sorting = $mSearch2->getSortFields($sort);
            if ($sorting == '`Data`.`price` desc') {
                $sorting = 'IF (`Data`.`price` > 0, 1, 0) desc, `Data`.`price` desc';
            } elseif ($sorting == '`Data`.`price` asc') {
                $sorting = 'IF (`Data`.`price` > 0, 1, 0) desc, `Data`.`price` asc';
            }
        }
        $paginatorProperties['sortby'] = $sorting;
Радуемся
Anton
13 января 2022, 20:48
2
+6
Всем привет!

Я борюсь таким образом:
1. Проверяю в сниппете для FormIt с помощи регулярки на наличие ссылки типа site.com И email типа name@mail.com в поле текстового сообщения + в каждом поле такую проверку делаю (Имя, Телефон, Название фирмы, все поля чекаю). Если обнаружена ссылка, то возвращаю false и сообщение, что «Ссылки или электронные адреса в данном поле не разрешены. Вы можете приложить Word, Excel, PDF или текстовый файл с подробным описанием, ссылками и email.».
2. Использую сниппет csrfhelper:
<input type="hidden" name="csrf_token" value="[[!csrfhelper? &key=`contact-us`]]">
А в вызов FormIt добавляю хук csrfhelper_formit.
3. Разумеется, добавляю простейшую проверку на пустоту скрытое поле URL (можно назвать поле любым человекоподобным словом)
<input class="form-control visually-hidden" type="text" name="url" value="">
4. Добавляю таймер на страницу, который не разрешает отправлять форму, если ты менее 15 секунд на странице (обычно спам-боты отправляют спам моментально после загрузки страницы, менее чем за 1-5 секунд) и показываю пользователю сообщение об ошибке «Слишком быстро нажата кнопка Отправить. Подождите, пожалуйста, 15 секунд и отправьте еще раз.»
В форме Fenom-тэг с текущим временем в формате UNIX. Сравниваем его в антиспам-сниппете.
<input class="form-control visually-hidden" type="text" name="formtime" value="{time()}">
5. На всякий случай вырезаю все html-тэги из всех полей :stripTags
6. В доработке хук, который чекает прилагаемый файл через форму: только pdf, word, excel, txt, zip, png, jpg; не больше 25 мегабайт (вроде как стандартное ограничение всеми почтовиками). Если проверка не пройдена, то показываем вежливое сообщение, что мы принимаем только такие файлы и не больше 25 мб.
7. Не забываем про проверку на обязательность заполнения полей со стороны клиента с помощью тэга required. А также проставляем все необходимы типы данных в инпутах: type=«email», type=«number» или же type=«text» во всех полях!

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

{$_modx->runSnippet('!FormIt', [

	'hooks' => 'antiSpam,spam,csrfhelper_formit,email',

	'spamCheckIp' => '1',
	'csrfKey' => 'contact-us',

	'validate' => 'name:stripTags:required,email:stripTags:required,message:stripTags:required,url:blank',

	'successMessage' => '<div class="alert alert-success mt-3" role="alert"><b>Спасибо!</b> Ваше сообщение было отправлено.</div>'

])}
Сам сниппет antiSpam:
<?php
$name = $hook->getValue('name');
$phone = $hook->getValue('phone');
$email = $hook->getValue('email');
$message = $hook->getValue('message');
$contact_attachment = $hook->getValue('contact_attachment');

$regExpGSM = '/(\d{3})(?:(?:-*|\s*|\.*|\)*|\)\s*))(\d{3})(?:(?:-*|\s*|\.*|\)*))(\d{3})/';
$regExpURL = '/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b[-a-zA-Z0-9@:%_\+.~#?&=]*/i';
$regExpEmail = '/(?<=\b)\w([\w\.\-_0-9])*(@| at )[\w0-9][\w\-_0-9]*((\.| DOT )[\w\-_0-9]+)+(?=\b)/';
$regExpExt = '/^([A-Za-z0-9-_.])+\.(png|jpe?g|gif|bmp)$/';

/*Check regExp email*/
if (preg_match($regExpEmail, $name)) {
    $hook->addError('emailInName', 'Email-адреса не разрешены в поле Имя. Вы можете приложить Word, Excel, PDF или текстовый файл с подробным описанием, ссылками и электронной почтой.');
    return false;
} elseif (preg_match($regExpEmail, $message)) {
    $hook->addError('emailInMessage', 'Email-адреса не разрешены в поле Сообщения. Вы можете приложить Word, Excel, PDF или текстовый файл с подробным описанием, ссылками и электронной почтой.');
    return false;

    /*Check regExp URL's*/
} elseif (preg_match($regExpURL, $name)) {
    $hook->addError('linkInName', 'Ссылки на сайты не разрешены в поле Имя. Вы можете приложить Word, Excel, PDF или текстовый файл с подробным описанием, ссылками и электронной почтой.');
    return false;
} elseif (preg_match($regExpURL, $message)) {
    $hook->addError('linkInMessage', 'Ссылки на сайты не разрешены в поле Сообщения. Вы можете приложить Word, Excel, PDF или текстовый файл с подробным описанием, ссылками и электронной почтой.');
    return false;
	
/*И так далее по всем полям чекаю регулярками выше*/

/*Проверяем, что не слишком быстро нажимается кнопка "Отправить"*/
} elseif ($_POST['formtime'] > time() - 15) {
    $hook->addError('fastSending', 'Слишком быстро нажата кнопка Отправить. Подождите, пожалуйста, 15 секунд и отправьте еще раз.');
    return false;

} else {
    return true;
}
В самой форме плейсхолдеры для вывода человекопонятных ошибок:
[[!+fi.error.csrf_token:notempty=`
<div class="alert alert-warning mt-3 mb-2">[[!+fi.error.csrf_token]]</div>
`]]
[[!+fi.error.fastSending:notempty=`
<div class="alert alert-warning mt-3 mb-2">[[!+fi.error.fastSending]]</div>
`]]

[[+fi.statusMessage]]
[[+fi.successMessage]]

<input type="text" class="form-control"
       placeholder="Ваше имя *"
       name="name"
       value="[[!+fi.name]]"
       required>
<span class="error_name">[[+fi.error.name]]</span>
До этого у меня была всего-лишь одна проверка regExp на наличие ссылки и email в поле Сообщения (message). Спама почти не было, но стал приходить спам типа Buy our viagra on usa-pharma.shop, пропускались такие ссылки. Более длинная регулярка уже иногда блочила любой текст, если юзер пишет сообщение и после точки сразу пишет слово нового предложения.

Добавил проверку на 15 секундное ожидание (человеку просто не реально раньше 15й секунды заполнить все поля и отправить форму). Можно поставить 5 секунд.

После этого стали приходить сообщение только от человеков, никакого спама.

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

Ну и можно по IP блочить вдобавок, как выше уже предложено.

Я не очень силен в PHP, как смог так и написал, поэтому большая просьба помочь хорошо доработать данный сниппет/хук и поделиться им здесь.
Валерий
21 ноября 2021, 20:20
1
0
Проблему решил. Добавил в вызов фильтра:
&leftJoin=`{"Remains":{"class":"msprRemains","on":"msProduct.id = Remains.product_id AND Remains.remains > 0"}}`
&loadModels=`msproductremains`
&groupby=`msProduct.id`
&select=`{"msProduct":"*","Remains":"SUM(Remains.remains) as remains"}`
Илья
14 июля 2020, 13:15
2
0
Сделал так:
{'!pdoNeighbors' | snippet : [
    'class' => 'modResource',
        'loadModels' => 'ms2gallery',
        'leftJoin' => [
            'Image' => [
                'class' => 'msResourceFile',
                'on' => 'modResource.id = Image.resource_id AND Image.parent = 0 AND Image.rank = 0',
            ],
            'Thumb' => [
                'class' => 'msResourceFile',
                'on' => 'Image.id = Thumb.parent AND Thumb.path LIKE "%small%"',
            ]
        ],
        'select' => [
            'modResource' => '*',
            'Thumb' => 'Thumb.url as small',
        ],
    'tplWrapper' => '@INLINE {$prev}{$next}',
    'tplPrev' => '@INLINE <a href="/{$uri}"><img src="{$small}">туда</a>',
    'tplNext' => '@INLINE <a href="/{$uri}"><img src="{$small}">сюда</a>',
]}
В результате выводится первая миниатюра {$small} в pdoNeighbors
Может кому-то будет полезно.

Не дадим modxУ погибнуть!!!
Сергей Фещуков
20 июня 2019, 06:55
1
0
Попробуйте вот так:
[[!msProducts?
  &leftJoin=`{"Remains":{"class":"msprRemains","on":"msProduct.id = Remains.product_id AND Remains.remains > 0"}}`
  &groupby=`msProduct.id`
  &select=`{"msProduct":"*","Remains":"IF (Remains.remains > 0, 1, 0) as remains"}`
  &sortby=`Remains.remains`
  &sortdir=`DESC`
]]
Степан Прищепенко
27 марта 2019, 00:20
1
0
Приветствую, хороший компонент, но хотел отметить несколько вещей:
1. в папке модели должен быть metadata.mysql.php, хотя бы
с таким содержимым:
$xpdo_meta_map = array();
Иначе ловим ошибку.

2. В основном классе, метод инициализации есть, но не вызывается,
и там же pdo подключается, но не получает конфиг, следует
параметры в него не передашь, можно исправить вот так:

if ($this->pdoTools = $this->modx->getService('pdoTools')){
	  $this->pdoTools->setConfig($this->config, false);
	}
3. Ключ (хеш) конфигурации корзины, при ajax запросе передается,
но нинциализация не происходит, это значит как минимум pdo не получит конфиг из пункта 2.
Для этого инициализация может быть такой:

protected $initialized = false;
	public function initialize($config = array())
    {
        if ($this->initialized) return true;

        $this->config = array_merge($this->config, $config);

        if ($this->miniShop2 = $this->modx->getService('miniShop2')) {
            $this->miniShop2->initialize($this->ctx);
        }
        if ($this->pdoTools = $this->modx->getService('pdoTools')){
            $this->pdoTools->setConfig($this->config, false);
        }

        $this->initialized = true;
    }
А в handleRequest:

$tmp = $this->getSessionData();
    $tmp = is_array($tmp) ? $tmp : array();
    $this->initialize($tmp);
Ну и в сниппеты и плагины после успешной подгрузки класса:
$msMCD->initialize();
4. Чанк элемента в корзине содержит:
<input type="hidden" name="msmcd_id" value="{$item.id}">
Ошибкой было называть имя msmcd_id вместо id, так как это может сломать
работу других компонентов
, висящих на событиях изменения
товаров в корзине. Да и префикс msmcd_ не используется ни где.

5. При удалении товара вместо Cart.remove.response.success
нужно использовать:
miniShop2.Callbacks.add('Cart.remove.ajax.always', 'msmcd_remove', function(response) {
            var cur_responce = response.responseJSON || {}
            // console.log ("done args",cur_responce);
            // debugger;
            if (cur_responce && cur_responce.success){
                if ($('.ms2_product .msmcd-count').length) {
                    $(msMCDMiniCart.formId + k + ' ' + msMCDCount.msmcdCount).val(0);
                    $(msMCDMiniCart.formId + k + ' ' + msMCDCount.msmcdAction).val('cart/add');
                }

                var sendData = {
                    action: 'msmcd/chunk',
                    ctx: msMCDMiniCart.ctx
                };
                msMCDMiniCart.send(sendData);
            }
        });
Тогда будет правильно удалять товары.

Вроде после такого напильника, стало работать как нужно. Желаю творческих успехов!
Илья
30 мая 2017, 18:22
1
0
Спасибо за разъяснения. Очень помогли. В доках оказывается есть подобное, но не сразу было понятно.
Может кому пригодится, если нужно сделать сортировку по опциям товара miniShop2:
2 поля size_h и color_id, которые нам надо отсортировать
&leftJoin=`
	{
            "size_h":{
                "class":"msProductOption",
                "on":"size_h.product_id = msProduct.id AND size_h.key = 'size_h'"
            },
            "color":{
                "class":"msProductOption",
                "on":"color.product_id = msProduct.id AND color.key = 'color_id'" 
            }
        }`
&sortAliases=`{"size_h":"size_h","color":"color"}` // добавляем псевдоним
&sort=`size_w|value:desc,color|value:asc`
Prihod
13 января 2017, 13:59
2
+1
1) т/к нужно для всех товаров не попавших в обновление поменять значение availability то возможно целесообразней использовать в качестве условия для получения всех товаров class_key == msProduct
2) т/к availability (из инфы от автора вопроса) для товара добавлено через систему плагинов minishop2 которую также в удобной форме реализует дополнение msFieldsManager то код примет следующий вид
<?php
/** @var modX $modx */
switch ($modx->event->name) {
    case 'msieOnCompleteImportProduct':

        // Получаем id всех импортированных товаров
        $currentIDs = explode(",", $modx->event->params['data']);

        // Получаем id ВСЕХ товаров в магазине
        $q = $modx->newQuery('msProduct', array('class_key' => 'msProduct'));
        $q->prepare();
        $q->stmt->execute();
        $oldIDs = $q->stmt->fetchAll(PDO::FETCH_COLUMN, 0);

        // Вычисляем id товаров которые есть на сайте, но нет в файле импорта
        $oldIDs = array_diff($oldIDs, $currentIDs);

        if (!empty($oldIDs)) {
            $oldIDs = implode(',', $oldIDs);
            $sql = "UPDATE {$modx->getTableName('msProductData')} SET availability = 0 WHERE id IN  ({$oldIDs});";
            $modx->exec($sql);
        }
        break;
}