ЧПУ фильтр mFilter2

Всем доброго времени суток!
Уже не в первый раз на своих проектах сталкивались с пожеланием сеошников, чтобы часть параметров фильтра была доступна ЧПУ-ссылками и чтобы это было достаточно универсально. Под катом пара решений, которые я старался пилить достаточно универсально, но всё же без небольших костылей не обошлось!)

Я долго копался и мучался как бы это сделать получше, даже в процессе задолбал немного Василия, за что приношу ему свои извинения.)

Код состоит из четырёх частей:
1. Плагин, который в цикле ищет реально существующий ресурс по ссылке, начиная от полной, постепенно обрезая её по слешу.
2. Джаваскрипта, который содержит функцию тринслитераци, перезаписывает метод mSearch2.Hash.set и кусочек для подстановки значений числовых фильтров. Его необходимо поставить между закрытиями тегов body и html.
3. Добавления параметра data-text инпутам. Это нужно для того, чтобы формировать красивые ссылки для фильтра по родителям. Возможно кто-то предложит более красивое решение, но мне было как-то лень думать.
4. Добавления параметра data-url="[[~[[*id]]]] к #mse2_mfilter. Нужен для определения реальной ссылки страницы.
5. Сниппета translit_str для обратной, в нашем случае, транслитерации.

Также я использовал новый параметр фильтра aliases, чтобы уйти от таких приставок, как ms| и т.д.

В процессе работы я ушёл слишком далеко, и первым моим вариантом был «полный» ЧПУ фильтр, который превращал абсолютно все параметры в ЧПУ.
«Полный» ЧПУ фильтр:
Код плагина:
<?php
if ($modx->event->name != 'OnPageNotFound') {return false;}
$alias = $modx->context->getOption('request_param_alias', 'q');
if (!isset($_REQUEST[$alias])) {return false;}

$request = $page = trim($_REQUEST[$alias], "/");
$tmp = explode('/', $request);

for ($i = count($tmp); $i > 0; $i--) {
    // Определяем id раздела.
    if ($section = $modx->findResource($page . '/')) break;
    $page = trim(str_replace($tmp[$i-1], '', $page), "/");
}
 
if (!$section) return false;
$tmp = explode('/', trim(str_replace($page, '', $request), "/"));

$filter_params = array( // Массив соответствия алиаса параметру фильтра
    );
    
$alias = array( // Массив соответствия алиаса столбцу в БД
        'color' => 'external_signs',
        'type' => 'product_type'
    );

$check = false; // Обнуляем проверку
foreach ($tmp as $filter) {
    if ($filter == '') continue;
    $filter_arr = explode('-', $filter);
    // Получаем параметр и значение
    $param = array_shift($filter_arr);
    $value = implode('-', $filter_arr); $filter_val = false;
    $filter_param = array_key_exists($param, $filter_params) ? $filter_params[$param] : $param;
    
    if ($param == 'series') {
        $value = str_replace('_',' ',str_replace("'","\'",$value));
        $value = '\''.str_replace(',','\',\'',$value).'\'';
        
        $query = new xPDOCriteria($modx, "SELECT id
                                            FROM modx_site_content
                                            WHERE
                                                pagetitle IN ($value)");
        if ($query->prepare() && $query->stmt->execute()) {
            $result_arr = $query->stmt->fetchAll(PDO::FETCH_ASSOC);
            foreach($result_arr as $result) {
                $filter_val .= $result['id'].',';
            }
        }
        else return false;
        
        // Присваиваем значение категории в переменную
        $filter_val = trim($filter_val, ",");
    }
    elseif ($param == 'price' || $param == 'sort' || $param == 'tpl' || $param == 'page') {
        // Получаем диапазон цен
        $filter_val = str_replace('-',',',$value);
    }
    else {
        $value_tr = $modx->runSnippet('translit_str', array('input' => $value, 'options' => 're'));
        $column_name = array_key_exists($param, $alias) ? $alias[$param] : $param;
        
        $value_tr = str_replace(' усб',' usb', str_replace(' тв',' tv', $value_tr));
        
        $value_tr = '\''.str_replace(',','\',\'',$value_tr).'\'';
        $value_fix = str_replace(' ','-',$value_tr);
        $value = '\''.str_replace(',','\',\'',str_replace("'","\'",$value)).'\'';
        
        $query = new xPDOCriteria($modx, "SELECT $column_name
                                            FROM modx_ms2_products
                                            WHERE
                                                $column_name IN ($value)
                                                OR
                                                $column_name IN ($value_tr)
                                                OR
                                                $column_name IN ($value_fix)");
                                                
        if ($query->prepare() && $query->stmt->execute()) {
            $result_arr = $query->stmt->fetchAll(PDO::FETCH_ASSOC);
            foreach($result_arr as $result) {
                $filter_val .= $result[$column_name].',';
            }
        }
        else return false;
        
        // Присваиваем значение фильтра в переменную
        $filter_val = trim($filter_val, ",");
    }
        
    // Осталось выставить нужные переменные в запрос, как будто юзер их сам указал
    if ($filter_val) {$_GET[$filter_param] = $_REQUEST[$filter_param] = $filter_val; $check = true;}
}

// Есть ли параметры
if ($check) {
	// А теперь подсовывем юзеру страницу, а дальше сниппет на ней сам разберётся
	$modx->sendForward($section);
}
// Иначе ничего не делаем и юзер получает 404 или его перехватывает другой плагин.
Сниппет translit_str:
<?php
//Массив транслитерации
$translit = array(
            'а' => 'a',   'б' => 'b',   'в' => 'v',
            'г' => 'g',   'д' => 'd',   'е' => 'e',
            'ё' => 'yo',   'ж' => 'zh',  'з' => 'z',
            'и' => 'i',   'й' => 'j',   'к' => 'k',
            'л' => 'l',   'м' => 'm',   'н' => 'n',
            'о' => 'o',   'п' => 'p',   'р' => 'r',
            'с' => 's',   'т' => 't',   'у' => 'u',
            'ф' => 'f',   'х' => 'x',   'ц' => 'c',
            'ч' => 'ch',  'ш' => 'sh',  'щ' => 'shh',
            'ь' => '\'',  'ы' => 'y',   'ъ' => '\'\'',
            'э' => 'e\'',   'ю' => 'yu',  'я' => 'ya',
            'А' => 'A',   'Б' => 'B',   'В' => 'V',
            'Г' => 'G',   'Д' => 'D',   'Е' => 'E',
            'Ё' => 'YO',   'Ж' => 'Zh',  'З' => 'Z',
            'И' => 'I',   'Й' => 'J',   'К' => 'K',
            'Л' => 'L',   'М' => 'M',   'Н' => 'N',
            'О' => 'O',   'П' => 'P',   'Р' => 'R',
            'С' => 'S',   'Т' => 'T',   'У' => 'U',
            'Ф' => 'F',   'Х' => 'X',   'Ц' => 'C',
            'Ч' => 'CH',  'Ш' => 'SH',  'Щ' => 'SHH',
            'Ы' => 'Y',   'Ь' => '\'',  'Ъ' => '\'\'',
            'Э' => 'E\'',   'Ю' => 'YU',  'Я' => 'YA',
            ' ' => '_'
        );
///////////////

$translit = $options == 're' ? array_flip($translit) : $translit;

return strtr($input, $translit);
Код чанка:
<label for="mse2_[[+table]][[+delimeter]][[+filter]]_[[+idx]]" class="[[+disabled]] filter-label [[+checked]]">
	<input class="filter-checkbox" type="checkbox" name="[[+filter_key]]" id="mse2_[[+table]][[+delimeter]][[+filter]]_[[+idx]]" value="[[+value]]" data-text="[[+title]]" [[+checked]] [[+disabled]]/> [[+title]] <sup>[[+num]]</sup>
</label>
Джаваскрипт:
//Если с английского на русский, то передаём вторым параметром true.
transliterate = (
	function() {
		var
			rus = "щ   ш  ч  ц  ю  я  ё  ж  ъ  ы  э  а б в г д е з и й к л м н о п р с т у ф х ь".split(/ +/g),
			eng = "shh sh ch cz yu ya yo zh '' y  e' a b v g d e z i j k l m n o p r s t u f x '".split(/ +/g)
		;
		return function(text, engToRus) {
			var x;
			for(x = 0; x < rus.length; x++) {
				text = text.split(engToRus ? eng[x] : rus[x]).join(engToRus ? rus[x] : eng[x]);
				text = text.split(engToRus ? eng[x].toUpperCase() : rus[x].toUpperCase()).join(engToRus ? rus[x].toUpperCase() : eng[x].toUpperCase());	
			}
			text = text.replace(/ /g,"-");
			return text.toLowerCase();
		}
	}
)();

// Set alias filter
mSearch2.Hash.set = function(vars) {
    var hash = ''; var vars_arr = [];
    var curr_path = $(mSearch2.options.wrapper).data('url');
	for (var i in vars) {
	    var vars_tmp = '';
		if (vars.hasOwnProperty(i)) {
		    if (!$('fieldset.filter-'+i).hasClass('filter-number') && $("input[name='"+i+"']").length) {
    		    vars_arr = vars[i].split(',');
                for (var j = 0; j < vars_arr.length; j++) {
                    vars_tmp += ',' + $("input[name='"+i+"'][value='"+vars_arr[j]+"']").data("text");
                }
		    }
		    else vars_tmp = ','+vars[i];
		    
			hash += i + '-' + transliterate(vars_tmp.substr(1)) + '/';
		}
	}
	
	if (!this.oldbrowser()) {
		window.history.pushState({mSearch2: curr_path + hash}, '', curr_path + hash);
	}
	else {
		window.location.hash = hash;
	}
};

// Set slider numbers
$(".filter-number").each(function() {
    var filter = $(".mse2_number_slider", this).data('number-filter'),
        re_str = "^(.*)\\/"+filter+"(-|=)(\\d{1,5}),(\\d{1,5})\\/(.*)?$",
        re = new RegExp(re_str),
        match_arr = document.location.pathname.match(re);
        
    if (match_arr) {
        min = match_arr[3], max = match_arr[4];
        $(".mse2-number-0", this).val(min); $(".mse2-number-1", this).val(max);
        $(mSearch2.options.slider, this).slider('values',0,min); // sets first handle (index 0)
        $(mSearch2.options.slider, this).slider('values',1,max); // sets second handle (index 1)
    }
});
— После всего вышенаписанного я отрапортовал СЕОшникам, на что получал ответ, что мол «всё круто, но ты немного перестарался».
Задача от них состояла в том, чтобы такие вещи, как tpl, sort и price в ЧПУ даже и не лезли, и чтобы когда присутствуют более чем 1 выбранный параметр одной характеристики, то он тоже записывался как get. Немного подумав, я родил следующее.

ЧПУ фильтр, который СЕОшники назвали «идеальным»:
Код плагина:
<?php
if ($modx->event->name != 'OnPageNotFound') {return false;}
$alias = $modx->context->getOption('request_param_alias', 'q');
if (!isset($_REQUEST[$alias])) {return false;}

$request = $page = trim($_REQUEST[$alias], "/");
$tmp = explode('/', $request);

for ($i = count($tmp); $i > 0; $i--) {
    // Определяем id раздела.
    if ($section = $modx->findResource($page . '/')) break;
    $page = trim(str_replace($tmp[$i-1], '', $page), "/");
}
 
if (!$section) return false;
$tmp = explode('/', trim(str_replace($page, '', $request), "/"));

$filter_params = array( // Массив соответствия алиаса параметру фильтра
    );
    
$alias = array( // Массив соответствия алиаса столбцу в БД
        'color' => 'external_signs',
        'type' => 'product_type'
    );

$check = false; // Обнуляем проверку
foreach ($tmp as $filter) {
    if ($filter == '') continue;
    $filter_arr = explode('-', $filter);
    // Получаем параметр и значение
    $param = array_shift($filter_arr);
    $value = implode('-', $filter_arr); $filter_val = false;
    $filter_param = array_key_exists($param, $filter_params) ? $filter_params[$param] : $param;
    
    if ($param == 'series') {
        $value = str_replace('_',' ',str_replace("'","\'",$value));
        
        $query = new xPDOCriteria($modx, "SELECT id
                                            FROM modx_site_content
                                            WHERE
                                                pagetitle LIKE '$value'");
        if ($query->prepare() && $query->stmt->execute()) {
            $result = $query->stmt->fetchAll(PDO::FETCH_COLUMN);
            $filter_val .= $result[0];
        }
        else return false;
        // Присваиваем значение категории в переменную
        $filter_val = trim($filter_val, ",");
    }
    elseif ($param == 'page') {
        // Получаем страницы
        $filter_val = str_replace('-',',',$value);
    }
    elseif ($param != 'price' || $param != 'sort' || $param != 'tpl') {
        $value = str_replace("'", "\'", $value);
        $value_tr = $modx->runSnippet('translit_str', array('input' => $value, 'options' => 're'));
        $column_name = array_key_exists($param, $alias) ? $alias[$param] : $param;
        
        $value_tr = str_replace(' усб',' usb', str_replace(' тв',' tv', $value_tr));
        $value_fix = str_replace(' ','-',$value_tr);
        
        $query = new xPDOCriteria($modx, "SELECT $column_name
                                            FROM modx_ms2_products
                                            WHERE
                                                $column_name LIKE '$value'
                                                OR
                                                $column_name LIKE '$value_tr'
                                                OR
                                                $column_name LIKE '$value_fix'");
                                                
        if ($query->prepare() && $query->stmt->execute()) {
            $result = $query->stmt->fetchAll(PDO::FETCH_COLUMN);
            $filter_val .= $result[0].',';
        }
        else return false;
        
        // Присваиваем значение фильтра в переменную
        $filter_val = trim($filter_val, ",");
    }
        
    // Осталось выставить нужные переменные в запрос, как будто юзер их сам указал
    if ($filter_val) {$_GET[$filter_param] = $_REQUEST[$filter_param] = $filter_val; $check = true;}
}

// Есть ли параметры
if ($check) {
	// А теперь подсовывем юзеру страницу, а дальше сниппет на ней сам разберётся
	$modx->sendForward($section);
}
// Иначе ничего не делаем и юзер получает 404 или его перехватывает другой плагин.
Сниппет translit_str (остался неизменным, но всё же продублирую):
<?php
//Массив транслитерации
$translit = array(
            'а' => 'a',   'б' => 'b',   'в' => 'v',
            'г' => 'g',   'д' => 'd',   'е' => 'e',
            'ё' => 'yo',   'ж' => 'zh',  'з' => 'z',
            'и' => 'i',   'й' => 'j',   'к' => 'k',
            'л' => 'l',   'м' => 'm',   'н' => 'n',
            'о' => 'o',   'п' => 'p',   'р' => 'r',
            'с' => 's',   'т' => 't',   'у' => 'u',
            'ф' => 'f',   'х' => 'x',   'ц' => 'c',
            'ч' => 'ch',  'ш' => 'sh',  'щ' => 'shh',
            'ь' => '\'',  'ы' => 'y',   'ъ' => '\'\'',
            'э' => 'e\'',   'ю' => 'yu',  'я' => 'ya',
            'А' => 'A',   'Б' => 'B',   'В' => 'V',
            'Г' => 'G',   'Д' => 'D',   'Е' => 'E',
            'Ё' => 'YO',   'Ж' => 'Zh',  'З' => 'Z',
            'И' => 'I',   'Й' => 'J',   'К' => 'K',
            'Л' => 'L',   'М' => 'M',   'Н' => 'N',
            'О' => 'O',   'П' => 'P',   'Р' => 'R',
            'С' => 'S',   'Т' => 'T',   'У' => 'U',
            'Ф' => 'F',   'Х' => 'X',   'Ц' => 'C',
            'Ч' => 'CH',  'Ш' => 'SH',  'Щ' => 'SHH',
            'Ы' => 'Y',   'Ь' => '\'',  'Ъ' => '\'\'',
            'Э' => 'E\'',   'Ю' => 'YU',  'Я' => 'YA',
            ' ' => '_'
        );
///////////////

$translit = $options == 're' ? array_flip($translit) : $translit;

return strtr($input, $translit);
Код чанка (остался неизменным, но всё же продублирую):
<label for="mse2_[[+table]][[+delimeter]][[+filter]]_[[+idx]]" class="[[+disabled]] filter-label [[+checked]]">
	<input class="filter-checkbox" type="checkbox" name="[[+filter_key]]" id="mse2_[[+table]][[+delimeter]][[+filter]]_[[+idx]]" value="[[+value]]" data-text="[[+title]]" [[+checked]] [[+disabled]]/> [[+title]] <sup>[[+num]]</sup>
</label>
Джаваскрипт:
//Если с английского на русский, то передаём вторым параметром true.
transliterate = (
	function() {
		var
			rus = "щ   ш  ч  ц  ю  я  ё  ж  ъ  ы  э  а б в г д е з и й к л м н о п р с т у ф х ь".split(/ +/g),
			eng = "shh sh ch cz yu ya yo zh '' y  e' a b v g d e z i j k l m n o p r s t u f x '".split(/ +/g)
		;
		return function(text, engToRus) {
			var x;
			for(x = 0; x < rus.length; x++) {
				text = text.split(engToRus ? eng[x] : rus[x]).join(engToRus ? rus[x] : eng[x]);
				text = text.split(engToRus ? eng[x].toUpperCase() : rus[x].toUpperCase()).join(engToRus ? rus[x].toUpperCase() : eng[x].toUpperCase());	
			}
			text = text.replace(/ /g,"-");
			return text.toLowerCase();
		}
	}
)();

// Set slider numbers
$(".filter-number").each(function() {
    var filter = $(".mse2_number_slider", this).data('number-filter'),
        re_str = "^(.*)\\/"+filter+"(-|=)(\\d{1,5}),(\\d{1,5})\\/(.*)?$",
        re = new RegExp(re_str),
        match_arr = document.location.pathname.match(re);
        
    if (match_arr) {
        min = match_arr[3], max = match_arr[4];
        $(".mse2-number-0", this).val(min); $(".mse2-number-1", this).val(max);
        $(mSearch2.options.slider, this).slider('values',0,min); // sets first handle (index 0)
        $(mSearch2.options.slider, this).slider('values',1,max); // sets second handle (index 1)
    }
});

// Set alias filter
mSearch2.Hash.set = function(vars) {
    var hash = '', hash_al = '', hash_get = ''; var vars_arr = [];
    var curr_path = $(mSearch2.options.wrapper).data('url');
	for (var i in vars) {
	    var vars_tmp = '';
		if (vars.hasOwnProperty(i)) {
		    vars_arr = vars[i].toString().split(',');
		    if (vars_arr.length == 1
		        && i != 'tpl'
		        && i != 'sort'
		        && i != 'price') {
                    vars_tmp = ',' + $("input[name='"+i+"'][value='"+vars[i]+"']").data("text");
                    hash_al += i + '-' + transliterate(vars_tmp.substr(1)) + '/';
		    }
		    else {
		        hash_get += '&' + i + '=' + vars[i];
		    }
		}
	}
	
	hash_get = hash_get === '' ? '' : '?' + hash_get.substr(1);
	hash = hash_al + hash_get;
	
	if (!this.oldbrowser()) {
		window.history.pushState({mSearch2: curr_path + hash}, '', curr_path + hash);
	}
	else {
		window.location.hash = hash;
	}
};
Вот такой вот, может быть немного кривой, но всё таки код. Возможно он кому-то будет полезным!

П.С. Совсем забыл, пример работы можно посмотреть вот тут jung-pro.ru/ramki.

Обновление от 27.02.16:
Добавил сниппет translit_str и поменял несколько строчек кода. Те, в которых по просьбе одних сеошников пробел ' ' заменялся на дефис '-'. Это было крайне неудобно и приводило к костылям.
Пока не доработан функционал для msOptions. Там надо дописывать отдельное условие для них, где будет идти выборка по таблице.
Для страждущих замены мета тегов, вот рабочий вариант — shop.bodybuilding.ua/katalog/protein/. Если очень надо, то выложу своё решение. Работает как для аякса, так и при первом входе.
Дмитрий Зарубин
21 февраля 2016, 23:06
modx.pro
42
8 337
+12

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

@ndrew
22 февраля 2016, 02:24
0
С одной стороны компонент mFilter2 выполняет все на 100% и чпу ему и не нужен.
А если посмотреть со стороны СЕО то это значит что под разными вариациями фильтра будут проиндексированы разные страницы одного и того же каталога но со своей СЕО составляющей!
Это однозначно очень полезный плагин с точки зрения СЕО!!!
Жаль что из коробки чпу нету.
    Дмитрий Зарубин
    22 февраля 2016, 02:33
    +1
    На самом деле его очень сложно (а может быть и невозможно) написать такой плагин абсолютно универсальным. Он все равно требует заточки под проект. Возможно именно поэтому автор mFilter2 и не делал этого, дабы не сыпались ему поддержку гневные обращения.
      @ndrew
      22 февраля 2016, 02:37
      0
      Я это указал в первом предложении ;)
      А ЧПУ нужен по любому если сайт не в директе продвигать на пример.
      Любое ЧПУ можно реализовать или на уровне кода или например средствами htaccess = костыли. Как то так.

      П. С. — но на самом деле все поисковики в наше время индексируют динамические ссылки так же как и ЧПУ!

      А все остальное от лукавого ;)
        Дмитрий Зарубин
        22 февраля 2016, 02:49
        +1
        Ну кстати да, на счёт индексации согласен.)
        Но СЕОшникам сложно что-то доказать. И лучше им не давать аргументов для ответа на вопрос «А почему сайт не продвигается?».)
    @ndrew
    22 февраля 2016, 03:10
    0
    В современном понимании сайт может не продвигаться по 100500 факторам и ЧПУ тут играет примерно 1% от общего пирога проблем ))
    А так то СЕОшники тоже кушать хотят. ;)

    П. С. — как гугл так и яндекс в тихоря возвращаются на старые алгоритмы взвешивания сайтов, а многие СЕОшники работают по инерции. ;)
      Павел Гвоздь
      22 февраля 2016, 08:39
      0
      как гугл так и яндекс в тихоря возвращаются на старые алгоритмы взвешивания сайтов
      Это откуда такие данные?
        Павел Гвоздь
        22 февраля 2016, 08:53
        +1
        То есть, тратили бабло, тратили, а потом такие:
        — А давай вернёмся на старые алгоритмы?!
        — А давай!


        И прощай будущее, здрасьте ссылки и дорвеи.
      Павел Гвоздь
      22 февраля 2016, 08:50
      +3
      Ну ок, сделал ты для СЕОшников эту плюху, которая зачем-то ЧПУ делает для фильтра (давно уже боты спокойно относятся к УРЛам любого вида), а СЕОшники тебя похвалили за такое замечательное ЧПУ, которое совершенно не сыграет никакой роли в поисковом продвижении. А как на счёт такого «совершенно не значимого» фактора, как Title и H1?
      Поясню… Я кликаю на фильтр по серии, УРЛ становится прикольным, ничё не говорю, а title и h1, как был «Рамки», так и остался. Это такое новое СЕО чтоли, я не пойму?) Увольте этих ваших СЕОшников, они напрасно тратят ваше время и деньги…

      P.S.: Всё, что вы прочли ранее — моё личное мнение, которое я никому не желаю навязывать.
        Ivan Shvindin
        22 февраля 2016, 09:20
        0
        Ты прав, кроме чпу для фильтров, им нужны h1, title и description, которые можно генерить по шаблону
        Хороший пример такой реализации можно увидеть на том же квадруме
          Сергей
          18 августа 2016, 11:10
          0
          Мы доработали этот плагин и сделали смену, относительно выбора свойств фильтра:
          — h1 меняется динамически
          — при перезагрузке h1 и title проставляются относительно выбранного
          — есть еще небольшие недочеты, которые вытачивать с точки зрения грамматики

          chtk.ru/production/
            Ivan Shvindin
            18 августа 2016, 11:17
            0
            а как же чпу при выбранных нескольких параметрах? это важно
              Сергей
              18 августа 2016, 11:58
              0
              В нашей специфике такой нужды — не было. А так — дорабатывать ))
          Дмитрий Зарубин
          22 февраля 2016, 15:00
          0
          Тайтл, h1 и тексты на странице также будут подхватываться именно те, которые надо. Для этого есть отдельный функционал. Просто они ещё не заполнены.
          Кирилл
          22 февраля 2016, 12:03
          +1
          На сайте присутствует баг, выбираю в фильтре «A creation», фильтруется и изменяется URL (ramki/series-a-creation/).
          Далее, жму «Пластик», опять все ок (ramki/series-a-creation/material-plastik/).
          Дергаю ползунок цены (ramki/series-a-creation/material-plastik/?price=6000,26505). Обратно возвращаю слайдер цены, чтобы фильтр по цене не учитывался. Вот тут и получается, что URL скидывается (ramki/), хотя чекбоксы на «A creation» и «Пластик» стоят.
            Дмитрий Зарубин
            22 февраля 2016, 15:00
            0
            Спасибо. Сейчас поправлю.
              Дмитрий Зарубин
              22 февраля 2016, 17:47
              1
              0
              Кирилл, покопавшись обнаружил, что это небольшой баг mFilter2, который завязан именно на категориях. Когда вы выбираете категорию, ставите фильтр цены (а возможно и любой ползунковый) так, что не находит результатов, а потом возвращаете его в прежнее положение, то урл очищается и остаётся только цена. Понял это по одному проекту, на котором пока нет ЧПУ и стоит более старая версия — shop.bodybuilding.ua/brendyi/bodybuildingua.html.
              В техподдержку по этому вопросу уже отписал, так что думаю пофиксят в ближайшее время.
                Дмитрий Зарубин
                23 февраля 2016, 11:09
                0
                Кирилл, Василий исправил баг. Можно обновлять пакет, если он у вас есть.)
                Наумов Алексей
                26 февраля 2016, 16:28
                0
                Дмитрий, сниппет translit_str как минимум потеряли вы! Дополните?
                  Дмитрий Зарубин
                  27 февраля 2016, 13:11
                  0
                  Точно. Прошу прощения, сейчас добавлю. Да и немного изменю этот код, потому что я уже его доработал чуток.
                    Наумов Алексей
                    27 февраля 2016, 15:42
                    1
                    0
                    Спасибо! Я тоже ковырял твой скрипт и много в нем успел переписать под себя:)

                    Расскажи в общих чертах, как теги и h1/ текст на странице меняешь (планируешь менять).

                    И еще такой вопрос: а как тот же яндекс начнет индексировать ссылки фильтра? Они же формируются ява скриптом, прямых ссылок на фильтры нигде нет…
                      Владимир Кисилица
                      27 февраля 2016, 16:07
                      0
                      Но их ведь можно добавить, в нужных местах, например вывести как теги в категории. Тогда эти ссылки проиндексируют роботы
                        Дмитрий Зарубин
                        27 февраля 2016, 23:02
                        +2
                        Если в общих чертах, то я использовал компонент MIGX и его TV типа migx. Данные она хранит в json. Потом я смотрю, есть ли у меня в этой твшке бренд или серия, которые сейчас в Гет, и вывожу значения в плейсхолдеры. Работает отлично. Могу разместить сниппет и аякс в отдельной заметке, если нужно. А пример вот тут shop.bodybuilding.ua/katalog/protein/. Работает и по аяксу, и по прямому переходу.
                        А по-поводу индексации Владимир сказал правильно. Просто размещаете те ссылки, которые вам надо и где надо. У меня в частности они идут со страницы товара — в хлебных крошках и в нижнем блоке shop.bodybuilding.ua/katalog/protein/muscle-tech-platinum-whey-909-g-pr-000099.html.
                          Наумов Алексей
                          28 февраля 2016, 10:32
                          0
                          Спасибо за подробные разъяснения!
                          Romasa
                          27 июля 2016, 15:36
                          0
                          Дмитрий Зарубин, получается
                          — я могу к каждому значению фильтра сделать свое ЧПУ+МЕТАДАННЫЕ?

                          Как получить плагин?)
                            Дмитрий Зарубин
                            28 июля 2016, 13:15
                            0
                            Предупреждаю сразу, откомментирован он крайне хреново.) Плюс его невозможно сделать универсальным, а под какие-то нюансы нужно подгонять.
                            <?php
                            if ($modx->event->name == 'OnPageNotFound') {
                                $alias = $modx->context->getOption('request_param_alias', 'q');
                                if (isset($_REQUEST[$alias])) {
                                    
                                    $request = $page = trim($_REQUEST[$alias], "/");
                                    $tmp = explode('/', $request);
                                    
                                    for ($i = count($tmp); $i > 0; $i--) {
                                        // Определяем id раздела.
                                        if ($section = $modx->findResource($page . '/')) break;
                                        $page = trim(str_replace($tmp[$i-1], '', $page), "/");
                                    }
                                     
                                    if ($section) {
                                        
                                        $tmp = explode('/', trim(str_replace($page, '', $request), "/"));
                                        
                                        $filter_params = array( // Массив соответствия алиаса параметру фильтра
                                            );
                                            
                                        $alias = array( // Массив соответствия алиаса столбцу в БД
                                                'brand' => 'manufacturer'
                                            );
                                        
                                        $check = false; // Обнуляем проверку
                                        foreach ($tmp as $filter) {
                                            if ($filter == '') continue;
                                            $filter_arr = explode('-', $filter);
                                            // Получаем параметр и значение
                                            $param = array_shift($filter_arr);
                                            $value = implode('-', $filter_arr); $filter_val = false;
                                            $filter_param = array_key_exists($param, $filter_params) ? $filter_params[$param] : $param;
                                            
                                            /*if ($param == 'series') {
                                                $value = str_replace('-',' ',str_replace("'","\'",$value));
                                                
                                                $query = new xPDOCriteria($modx, "SELECT id
                                                                                    FROM modx_site_content
                                                                                    WHERE
                                                                                        pagetitle LIKE '$value'");
                                                if ($query->prepare() && $query->stmt->execute()) {
                                                    $result = $query->stmt->fetchAll(PDO::FETCH_COLUMN);
                                                    $filter_val .= $result[0];
                                                }
                                                else return false;
                                                // Присваиваем значение категории в переменную
                                                $filter_val = trim($filter_val, ",");
                                            }
                                            else*/if ($param == 'page') {
                                                // Получаем страницы
                                                $filter_val = str_replace('-',',',$value);
                                            }
                                            elseif ($param != 'price' || $param != 'sort' || $param != 'tpl') {
                                                $value = str_replace("'", "\'", $value);
                                                $value_tr = $modx->runSnippet('translit_str', array('input' => $value, 'options' => 're'));
                                                $column_name = array_key_exists($param, $alias) ? $alias[$param] : $param;
                                                
                                                $value_tr = str_replace(' усб',' usb', str_replace(' тв',' tv', $value_tr));
                                                $value_fix = str_replace(' ','-',$value_tr);
                                                
                                                $query = new xPDOCriteria($modx, "SELECT $column_name
                                                                                    FROM modx_ms2_products
                                                                                    WHERE
                                                                                        $column_name LIKE '$value'
                                                                                        OR
                                                                                        $column_name LIKE '$value_tr'
                                                                                        OR
                                                                                        $column_name LIKE '$value_fix'");
                                                                                        
                                                if ($query->prepare() && $query->stmt->execute()) {
                                                    $result = $query->stmt->fetchAll(PDO::FETCH_COLUMN);
                                                    $filter_val .= $result[0].',';
                                                }
                                                
                                                // Присваиваем значение фильтра в переменную
                                                $filter_val = trim($filter_val, ",");
                                            }
                                                
                                            // Осталось выставить нужные переменные в запрос, как будто юзер их сам указал
                                            if ($filter_val) {$_GET[$filter_param] = $_REQUEST[$filter_param] = $filter_val; $check = true;}
                                        }
                                        
                                        // Есть ли параметры
                                        if ($check) {
                                        	// А теперь подсовывем юзеру страницу, а дальше сниппет на ней сам разберётся
                                        	$modx->sendForward($section);
                                        }
                                        // Иначе ничего не делаем и юзер получает 404 или его перехватывает другой плагин.
                                    }
                                }
                            }
                    Алексей Суслов
                    11 сентября 2016, 15:44
                    0
                    Дмитрий Зарубин, спасибо, что поделились своими наработками.
                    Подскажите, я правильно понимаю, что этот код не будет работать с фильтрами, данные которых закодированы в JSON (это теги, цвета, размеры)? В базе они выглядят так ["\u0416\u0435\u043d\u0441\u043a\u0430\u044f"]
                    Мучаюсь вот как бы этот код изменить на работу с такими полями, случайно не сможете подсказать, может вы уже делали это?
                      Дмитрий Зарубин
                      01 июня 2017, 14:07
                      0
                      Данная версия нет. Новая версия умеет и с опциями, и с вендорами, и с парентами. Ниже написал, что проект, на котором всё это реализовано, пока на стадии релиза и пока не знаю когда смогу сделать статью на это всё.
                      taxsin
                      05 марта 2017, 21:16
                      0

                      Добрый день. Все сделал по инструкции, но получилась какие-то ерунда( Что не так подскажите пожалуйста
                      Мартин Очоа
                      31 мая 2017, 14:34
                      +1
                      Здравствуйте, Дмитрий Зарубин! Наткнулся на вашу разработку — это практически то, что нужно для идеального SEO интернет-магазинов, за исклчением двух вещей:

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

                      2. И второй важный момент — это ЧПУ ссылки и мета-теги для двух и более параметров. Ведь на пересечении фильтров и кроется вся важность!

                      Т.е. если выбраны несколько параметров (например, ноутбуки, hp, матовый экран), то должна быть страница
                      site.ru/catalog/notebook/hp/matoviy/ и на этой странице должен быть h1 — «Ноутбуки HP с матовым экраном».

                      Такой функционал выведет продвижение Modx на новый уровень! Подобный функционал есть у 1С — marketplace.1c-bitrix.ru/solutions/sotbit.seometa#tab-install-link

                      Скажите, возможна ли доработка вашего модуля до функционала описанного выше?
                        Гриборий
                        01 июня 2017, 10:39
                        0
                        Подстановка контента – это же уж совсем индивидуально для каждого проекта. Вот тут принцип описан.
                          Дмитрий Зарубин
                          01 июня 2017, 14:06
                          +2
                          Добрый день!
                          Мы на одном из проектов, который запустится в ближайшее время, уже доработали всё что нужно. И чуть более правильный принцип работы ЧПУ, и учёт всех всех параметров со сменой мета и заголовков, а также возможность задавать кастомный для каждой ссылки.
                          Плюс всё это также работает по аяксу.
                          Пока не могу сказать точно когда доберусь, чтобы выложить всё это и описать принципы работы, но в планах есть.
                            Гриборий
                            01 июня 2017, 15:53
                            0
                            И как у вас хранится контент (заголовки, описание…)?
                              Дмитрий Зарубин
                              01 июня 2017, 16:02
                              0
                              TV MIGX, который в свою очередь представляет собой JSON. Там уже можно разгуляться как хочешь.
                              man
                              man
                              01 июня 2017, 16:08
                              0
                              Готовлю что-то подобное (делаю не по этому мануалу), но у меня пока процентов на 60 готовности. Хотелось бы посмотреть на альтернативные методы решения этой задачи.
                              Буду ждать с нетерпением статьи от вас)
                                Дмитрий Зарубин
                                01 июня 2017, 16:33
                                0
                                Не уверен, что это будет скоро.)
                                Но я обязательно сделаю апдейт этой статьи со ссылкой на новую.
                            Евгений Шеронов
                            01 июня 2017, 16:46
                            0
                            У меня подобное реализовано на одном проекте на MODX, изначально начинал с этой статьи. Спасибо Дмитрию.
                            Сейчас упаковываю в универсальный компонент.
                            С решением на Битрикс тоже знаком, частично им вдохновился, стоит на одном проекте.

                            Пересечение параметров также будет.
                            Если интересует, как пока работает на боевом сайте — напишите в скайп или на почту, поделюсь ссылкой.
                            Там даже есть склонение значений по падежам, но не уверен, что и это будет в компоненте)
                          Евгений Шеронов
                          09 августа 2017, 20:27
                          +3
                          Выложил дополнение SeoFilter, которое закрывает все вопросы по SEO и не требует сложных действий :)
                          modstore.pro/packages/ecommerce/seofilter
                            Дмитрий Зарубин
                            09 августа 2017, 20:49
                            +1
                            Таки интересно получилось. Даже наверно куплю для нового проекта.)
                            А если не секрет, то сколько на него ушло времени? Как в общем, так и чистого.)
                              Евгений Шеронов
                              10 августа 2017, 12:16
                              +2
                              Очень много :)
                              Начал совсем давно, месяца 3 назад.
                              Именно по часам не замерял, так как делал в свободное от проектов время.
                              В Modstore попала 14-ая версия компонента)

                              Только узнал, что phpStorm отслеживает время и активировав настройку увидел ужасающие цифры)
                              Ровно 7 дней x 24 часа = 168 часов я пробыл в IDE, переписывая по несколько раз логику)
                            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                            44