Последнее время очень часто возникают вопросы по поводу сортировки в наших любимых pdoResources, getResources и getProducts.
Следуя за сообществом в публикации решений на все случаи жизни, делюсь простым рецептом, на который можно везде давать ссылку :-).
Нужно два сниппета.
sortLink — формирует ссылки, при клике по которым происходит сортировка:
<?php
$url = $_SERVER['REQUEST_URI'];
list($url_part, $qs_part) = array_pad(explode("?", $url), 2, "");
parse_str($qs_part, $qs_vars);
unset($qs_vars['sort']);
unset($qs_vars['dir']);
unset($qs_vars['tv']);
unset($qs_vars['type']);
if (count($qs_vars) > 0) {
    $separ = '&';
    $url = $url_part."?".http_build_query($qs_vars);
} else {
    $separ = '?';
    $url = $url_part;
}
if($_GET['sort'] == $field and $_GET['dir'] == 'ASC') $arr='↑';
if($_GET['sort'] == $field and $_GET['dir'] == 'DESC') $arr='↓';
if(isset($tv)) $tv='&tv=1';
if($type=='number') $typ='&type=number';
if(!$_GET[$field]){
	if($_GET['dir'] == 'ASC'){
			$output = '<a href="'.$url.$separ.'sort='.$field.'&dir=DESC'.$typ.$tv.'">'.$name.''.$arr.'</a>';
		} else {
			$output = '<a href="'.$url.$separ.'sort='.$field.'&dir=ASC'.$typ.$tv.'">'.$name.''.$arr.'</a>';
		}
	} else {
		$output = '<a href="'.$url.$separ.'sort='.$field.'&dir=ASC'.$typ.$tv.'">'.$name.'</a>';
	}
return $output;

У него четыре параметра:
&field — название поля по которому сортировать (TV или стандартное поле ресурса).
&tv — если это TV-параметр установите &tv=`1`.
&type — тип поля (если это число, то установите &type=`number`).
&name — текст самой ссылки, который выводится на сайте.

Используем так:
[[!sortLink? &field=`pagetitle` &name=`По названию`]]
[[!sortLink? &field=`publishedon` &name=`По дате`]]
[[!sortLink? &field=`price` &type=`number` &tv=`1` &name=`По цене`]]

Данная конструкция выведет три ссылки, при клике по которым странице посредством GET-запроса будут передаваться нужные данные.

Второй сниппет Sort, который формирует собственно параметры сортировки:
<?php
$sort = $_GET['sort'];
$type = $_GET['type'];
$tv = $_GET['tv'];
if(isset($sort)) {
	if($tv == 1) {
		if($integer == 1) $integer = '&sortdirTVType=`integer`';
		$output = '
		&sortbyTV=`'.$sort.'`
		&sortdirTV=`'.$_GET['dir'].'`
		'.$integer.'
		';
	} else {
		$output = '
		&sortby=`'.$sort.'`
		&sortdir=`'.$_GET['dir'].'`
		';
	}
} else {
	if($defsort == '') $defsort = 'menuindex';
	if($defdir == '') $defdir = 'ASC';
	$output='
	&sortby=`'.$defsort.'`
	&sortdir=`'.$defdir.'`
	';
}
return $output;

У него три параметра:
&integer — если его активировать &integer=`1`, то в выводе появится параметр sortdirTVType, который есть у getProducts и getResources. Именно в этом случае сортировка по числовым значениям будет производиться нормальным образом. Для pdoResources его можно не использовать — достаточно просто тип ввода у TV-поля установить как «Число».

&defsort — сортировка по умолчанию при открытии страницы. Если оставить пустым или не указывать, сортируется по menuindex (позиции в меню/дереве ресурсов).

&defdir — направление сортировки по умолчанию при открытии страницы. Если оставить пустым или не указывать, сортируется от меньшего к большему (ASC).

Вызов сниппета Sort просто вставляем в вызов pdoResources, getResources или getProducts (никаких своих &sortby и &sortdir, естественно, уже не нужно).
pdoResources:
[[!pdoResources?
&parents=`...`
&tpl=`...`
&limit=`...`
[[!Sort? &defsort=`pagetitle`]]
]]

getProducts:
[[!getProducts?
&parents=`...`
&tpl=`...`
&limit=`...`
[[!Sort? &integer=`1` &defsort=`createdon` &defdir=`DESC`]]
]]


getResources:
[[!getResources?
&parents=`...`
&tpl=`...`
&limit=`...`
[[!Sort? &integer=`1`]]
]]


Естественно, с pdoPage это всё также работает:
[[!pdoPage?
&parents=`...`
&tpl=`...`
&limit=`...`
[[!Sort]]
]]

Как и с getPage:
[[!getPage?
&element=`getProducts`
&parents=`...`
&tpl=`...`
&limit=`...`
[[!Sort? &integer=`1`]]
]]


Обратите внимание — все сниппеты вызываются некэшированными.