Расширяем фильтрацию в категории товаров админ панели
Как это по итогу должно выглядеть.

Привожу простейший пример фильтрации товаров по наличию изображения.
ВАЖНО!
Мы не лезем в исходники самого компонента.
По данному примеру можно фильтровать по любому полю товара.
1) Создаем плагин на событие msOnManagerCustomCssJs
2) Подключаем туда наш файлик,
например, из папки assets/components/customAssets/category/filter/
Содержимое файла
За отображение фильтра отвечает вот этот вот кусочек
А за обработку нажатия вот это
Также стоит обратить внимание на то, что был переопределен процессор по умолчанию на
'mgr/product/getlist_custom'
Пример кастомного процессора.
Создаем файл в папке
core/components/minishop2/processors/mgr/product/getlist_custom.class.php
В нем переопределяем один из методов
Также один из кейсов: показать товары, созданные текущим пользователем (авторизован в админ панели)
Шаги те же самые, только переменная в extJs называется по другому (onlyMy) и в процессоре проще условие дописанное

Привожу простейший пример фильтрации товаров по наличию изображения.
ВАЖНО!
Мы не лезем в исходники самого компонента.
По данному примеру можно фильтровать по любому полю товара.
1) Создаем плагин на событие msOnManagerCustomCssJs
<?php
switch ($modx->event->name) {
case 'msOnManagerCustomCssJs':
$modx->controller->addLastJavascript(MODX_ASSETS_URL.'components/customAssets/category/filter/script.js);
break;
}
2) Подключаем туда наш файлик,
например, из папки assets/components/customAssets/category/filter/
Содержимое файла
if(Ext.ComponentMgr.types['minishop2-grid-products']) {
miniShop2.grid.ProductsCustom = function (config) {
Ext.applyIf(config, {
baseParams: {
action: 'mgr/product/getlist_custom',
},
});
miniShop2.grid.ProductsCustom.superclass.constructor.call(this, config);
};
Ext.extend(miniShop2.grid.ProductsCustom, Ext.ComponentMgr.types['minishop2-grid-products'], {
getTopBar: function () {
return [{
text: (MODx.config.mgr_tree_icon_msproduct ? String.format('<i class="{0}"></i> ', Ext.util.Format.htmlEncode(MODx.config.mgr_tree_icon_msproduct)) : '') + _('ms2_product_create'),
handler: this.createProduct,
scope: this
}, '-', {
text: (MODx.config.mgr_tree_icon_mscategory ? String.format('<i class="{0}"></i> ', Ext.util.Format.htmlEncode(MODx.config.mgr_tree_icon_mscategory)) : '') + _('ms2_category_create'),
handler: this.createCategory,
scope: this
}, '-', {
text: '<i class="icon icon-trash-o action-red"></i>',
handler: this._emptyRecycleBin,
scope: this,
}, '->', {
xtype: 'xcheckbox',
name: 'with_photo',
width: 170,
boxLabel: 'Только с фото',
ctCls: 'tbar-checkbox',
checked: MODx.config['ms2_category_show_only_with_photo'] == 1,
listeners: {
check: {fn: this.photoFilter, scope: this}
}
},{
xtype: 'xcheckbox',
name: 'nested',
width: 200,
boxLabel: _('ms2_category_show_nested'),
ctCls: 'tbar-checkbox',
checked: MODx.config['ms2_category_show_nested_products'] == 1,
listeners: {
check: {fn: this.nestedFilter, scope: this}
}
}, '-', this.getSearchField()];
},
photoFilter: function (checkbox, checked) {
var s = this.getStore();
s.baseParams.with_photo = checked ? 1 : 0;
this.getBottomToolbar().changePage(1);
},
});
Ext.reg('minishop2-grid-products', miniShop2.grid.ProductsCustom);
}
За отображение фильтра отвечает вот этот вот кусочек
{
xtype: 'xcheckbox',
name: 'with_photo',
width: 170,
boxLabel: 'Только с фото',
ctCls: 'tbar-checkbox',
checked: MODx.config['ms2_category_show_only_with_photo'] == 1,
listeners: {
check: {fn: this.photoFilter, scope: this}
}
}
А за обработку нажатия вот это
photoFilter: function (checkbox, checked) {
var s = this.getStore();
s.baseParams.with_photo = checked ? 1 : 0;
this.getBottomToolbar().changePage(1);
},
Также стоит обратить внимание на то, что был переопределен процессор по умолчанию на
'mgr/product/getlist_custom'
Пример кастомного процессора.
Создаем файл в папке
core/components/minishop2/processors/mgr/product/getlist_custom.class.php
В нем переопределяем один из методов
<?php
require_once(dirname(__FILE__) . '/getlist.class.php');
class msProductGetListProcessorCustom extends msProductGetListProcessor
{
/**
* @param xPDOQuery $c
*
* @return xPDOQuery
*/
public function prepareQueryBeforeCount(xPDOQuery $c)
{
$c->where(['class_key' => 'msProduct']);
$c->leftJoin('msProductData', 'Data', 'msProduct.id = Data.id');
$c->leftJoin('msCategoryMember', 'Member', 'msProduct.id = Member.product_id');
$c->leftJoin('msVendor', 'Vendor', 'Data.vendor = Vendor.id');
$c->leftJoin('msCategory', 'Category', 'Category.id = msProduct.parent');
if ($this->getProperty('combo')) {
$c->select('msProduct.id,msProduct.pagetitle,msProduct.context_key');
} else {
$c->select($this->modx->getSelectColumns('msProduct', 'msProduct'));
$c->select($this->modx->getSelectColumns('msProductData', 'Data', '', ['id'], true));
$c->select($this->modx->getSelectColumns('msVendor', 'Vendor', 'vendor_', ['name']));
$c->select($this->modx->getSelectColumns('msCategory', 'Category', 'category_', ['pagetitle']));
}
if ($this->item_id) {
$c->where(['msProduct.id' => $this->item_id]);
if ($parent = (int)$this->getProperty('parent')) {
$this->parent = $parent;
}
} else {
$query = trim($this->getProperty('query'));
if (!empty($query)) {
if (is_numeric($query)) {
$c->where([
'msProduct.id' => $query,
'OR:Data.article:=' => $query,
]);
} else {
$c->where([
'msProduct.pagetitle:LIKE' => "%{$query}%",
'OR:msProduct.longtitle:LIKE' => "%{$query}%",
'OR:msProduct.description:LIKE' => "%{$query}%",
'OR:msProduct.introtext:LIKE' => "%{$query}%",
'OR:Data.article:LIKE' => "%{$query}%",
'OR:Data.made_in:LIKE' => "%{$query}%",
'OR:Vendor.name:LIKE' => "%{$query}%",
'OR:Category.pagetitle:LIKE' => "%{$query}%",
]);
}
}
///////////////////////////////
$queryPhoto = $this->getProperty('with_photo', null);
$queryPhoto = ($queryPhoto === null) || (bool)$queryPhoto;
if (!empty($queryPhoto)) {
$c->query['where'][] = [
[
new xPDOQueryCondition(['sql' => 'Data.thumb IS NOT null', 'conjunction' => 'AND'])
]
];
} else {
$c->query['where'][] = [
[
new xPDOQueryCondition(['sql' => 'Data.thumb IS null', 'conjunction' => 'AND'])
]
];
}
///////////////////////////////
$parent = (int)$this->getProperty('parent');
if (!empty($parent)) {
$category = $this->modx->getObject('modResource', $parent);
$this->parent = $parent;
$parents = [$parent];
$nested = $this->getProperty('nested', null);
$nested = ($nested === null) && $this->modx->getOption(
'ms2_category_show_nested_products',
null,
true
) || (bool)$nested;
if ($nested) {
$tmp = $this->modx->getChildIds($parent, 10, ['context' => $category->get('context_key')]);
foreach ($tmp as $v) {
$parents[] = $v;
}
}
$parents = '(' . implode(',', $parents) . ')';
$c->query['where'][] = [
[
new xPDOQueryCondition(['sql' => 'msProduct.parent IN ' . $parents, 'conjunction' => 'OR']),
new xPDOQueryCondition(['sql' => 'Member.category_id IN ' . $parents, 'conjunction' => 'OR'])
]
];
}
}
$c->groupby($this->classKey . '.id');
return $c;
}
}
return 'msProductGetListProcessorCustom';
Также один из кейсов: показать товары, созданные текущим пользователем (авторизован в админ панели)
Шаги те же самые, только переменная в extJs называется по другому (onlyMy) и в процессоре проще условие дописанное
$onlyMy = $this->getProperty('onlyMy', null);
$onlyMy = ($onlyMy === null) || (bool)$onlyMy;
if (!empty($onlyMy)) {
$c->where([
'msProduct.createdby' => $this->modx->user->get('id'),
]);
}