За обновлениями можно следить в telegram-канале https://t.me/quasiart

Введение

Быстренько о том, как я реализовал мобильную версию сайта на базе MODX Revolution. Ранее я рассказывал о том, как создать мобильную версию сайта на CodeIgniter. Эти подходы к организации мобильной версии в обоих фреймворках кардинально не отличаются.

Задача

Основной сайт доступен по адресу site.ru, мобильная версия — m.site.ru. Если человек заходит на m.site.ru с ПК, то его перенаправляем на site.ru. Если человек заходит с мобильного устройства на site.ru, то он автоматически перенаправлется на m.site.ru. Не перенаправляется человек в единственном случае — если он сам решил, какую версию ему показывать.

Шаблоны

Обычно при правильном проектировании сайта на MODX количество шаблонов минимально. В моём случае не пришлось создавать отдельные шаблоны для полной и мобильной версии (учитывая, что различаться версии должны только оформлением, но не содержимым). Однако, внутри шаблонов чанки вызываются особым образом. Дело в том, что имя чанка создаётся динамически на основе того, какую версию сайта нужно отобразить. Например, у меня есть два чанка: header и headerMobile.

В шаблоне вызывается это дело следующим образом:

[[$header[[!SiteVersion]]]]

Если сниппет SiteVersion возвращает пустую строку, то отображается чанк header.

Если сниппет возвращает строку Mobile, то в шаблоне вызывается чанк headerMobile (происходит конкатенация имени чанка и вывода сниппета).

Ниже пример шаблона главной страницы. В самом начале вызывается некэшированный сниппет siteVersionRedirect, который решает, нужно ли перенаправить (redirect) посетителя на другую версию этой страницы. Например, если посетитель впервые зашёл на этот сайт (site.ru/articles) со смартфона, то сниппет перенаправит на мобильный адрес этой страницы (m.site.ru/articles).

[[!siteVersionRedirect]]
<!DOCTYPE html>
<html>
<head>
    [[$head[[!SiteVersion]]
</head>
<body>
    <div class="wrapper">
        <div class="container">
            [[$header[[!+site_version`]]]]
            [[$navigation[[!+site_version`]]]]
            [[$main[[!+site_version`]]]]
            [[$footer[[!+site_version`]]]]
        </div>
    [[scripts[[!+site_version]]]]
    </div>
</body>
</html>

Сниппеты

Для функционирования этой системы я создал 4 простых сниппета. Не всё идально, есть некоторые проблемы с производительностью и чистотой кода, но я решил опубликовать хотя бы этот вариант.

SiteMobileBrowser

Сниппет анализирует User-Agent браузера и решает, принадлежит он мобильному устройству или нет. Возвращает true или false.

$userUA = $modx-&gt;getOption('HTTP_USER_AGENT', $_SERVER, false);
$uas = array('Android', 'iPad', 'iPod', 'iPhone', 'Mini');
$isMobile = false;
foreach ($uas as &amp;$ua) {
	if (substr_count($userUA, $ua) &gt; 0) {
		$isMobile = true;
		break;
	}
}
return $isMobile;

SiteVersion

Возвращает строку Mobile или пустую строку. Используется для формирования имени чанка. Для того, чтобы не вызывать постоянно этот сниппет, я кладу вывод сниппета в плейсхолдер site_version. Таким образом, сниппет вызывается всего один раз, а затем его результат используется всё оставшееся время.

$version = $modx->getOption('site_version', $_SESSION, false);
$version = ($version == 'mobile' || ($modx->isMobileBrowser() && $version != 'desktop')) ? 'Mobile' : '';
$modx->setPlaceholder('site_version', $version);
return $version;

SiteVersionRedirect

Анализирует текущий адрес запроса и сессию пользователя, и если требуется, перенаправляет посетителя на моильную версию страницу.

$output = '';

$protocol = $modx->getOption('server_protocol').'://';
$host = $modx->getOption('HTTP_HOST', $_SERVER, 'quasi-art.ru');
$uri = $modx->getOption('REQUEST_URI', $_SERVER, '');

if (substr_count('/favicon.ico', $uri) > 0) {
	return;
}

if (substr($host, 0, strlen('www.')) === 'www.') {
	$host = str_replace('www.', '', $host);
}
if (substr($host, 0, strlen('m.')) === 'm.') {
	$host = str_replace('m.', '', $host);
}

if (!function_exists('isMobileDomain')) {
	function isMobileDomain() {
		global $modx;
		$host = $modx->getOption('HTTP_HOST', $_SERVER, false);
		if (!$host) {
		  	return false;
		}
		$hostArray = explode('.', $host);
		if (!is_array($hostArray)) {
		  	return false;
		}
	  	return (isset($hostArray[0]) && ($hostArray[0] == 'm'));
	}
}

$currentUrl = $protocol.$modx->getOption('HTTP_HOST', $_SERVER, 'quasi-art.ru').$uri;
$defaultUrl = $protocol.$host.$uri;
$desktopUrl = $protocol.$host.$uri;
$mobileUrl  = $protocol.'m.'.$host.$uri;

if ($modx->getOption('site_version', $_SESSION, false) == 'mobile') {
	// Если пользователь сам переключился на мобильную версию
	if (!isMobileDomain()) {
	  	$modx->sendRedirect($mobileUrl);
	}
} elseif ($modx->getOption('site_version', $_SESSION, false) == 'desktop') {
	// Если пользователь сам переключился на полную версию
	if (isMobileDomain()) {
	  	$modx->sendRedirect($desktopUrl);
	}
} else {
	// Автоматическое определение
	if ($modx->runSnippet('SiteMobileBrowser')) {
		if (!isMobileDomain()) {
		  	$modx->sendRedirect($mobileUrl);
		}
	} else {
		if (isMobileDomain()) {
		  	$modx->sendRedirect($desktopUrl);
		}
	}
}

SwitchToSiteVersion

Этот сниппет используется, когда человек решает вручную выбрать версию сайта.

$version = $modx->getOption('v', $scriptProperties, false);

if ($version == 'auto') {
	unset($_SESSION['site_version']);
} else {
	$_SESSION['site_version'] = $version;
}

$protocol = 'http://';
$host = $modx->getOption('HTTP_HOST', $_SERVER, 'quasi-art.ru');

if (substr($host, 0, strlen('www.')) === 'www.') {
	$host = str_replace('www.', '', $host);
}
if (substr($host, 0, strlen('m.')) === 'm.') {
	$host = str_replace('m.', '', $host);
}

$defaultUrl = $protocol.$host;
$desktopUrl = $protocol.$host;
$mobileUrl  = $protocol.'m.'.$host;

switch ($version) {
	case 'desktop':
		// Перенаправление на настольную
		$modx->sendRedirect($desktopUrl, array('type' => 'REDIRECT_META'));
		break;
	case 'mobile':
		// Перенаправление на мобильную
		$modx->sendRedirect($mobileUrl, array('type' => 'REDIRECT_META'));
		break;
	default:
		$modx->sendRedirect($modx->makeUrl(1), array('type' => 'REDIRECT_META'));
		break;
}

Ручное переключение версий

Любой посетитель запросто может поменять версию сайта, перейдя по специальной ссылке. Для этого я создал три ресурса (не обязательно, но можно сгруппировать их под другим ресурсом, как на рисунке). Первый ресурс для переключения на настольную версию, второй — мобильную, третий — для сброса настроек и автоматического определения.

Первый ресурс содержит следующий вызов:

[[!SwitchToSiteVersion? &v=`desktop`]]

Второй ресурс:

[[!SwitchToSiteVersion? &v=`mobile`]]

Третий

[[!SwitchToSiteVersion? &v=`auto`]]

У ресурсов рекомендую устанавливать пустой шаблон:

[[*content]]
Ресурсы для смены версии сайта
Ресурсы для смены версии сайта

Cookies

Чуть не забыл одну важную вещь — cookies для разных доменов будут изолированы. Чтобы исправить это, в настройках системы нужно изменить параметр session_cookie_domain. Он должен содержать доменное имя сайта с точкой в начале.

Объединить cookies для разных доменов
Объединить cookies для разных доменов

Готовые решения

Есть и готовые решения: https://rtfm.modx.com/extras/revo/modmobile. Только дата последнего релиза была более 10 лет назад :(

Вывод

Не скажу, что решение идеальное и универсальное, но уже прошло испытание на одном сайте. Единственной нерешённой проблемой является то, что кэшируется только одна версия страницы (которая загрузилась первой). Отображается, конечно, всё нормально, иначе я бы не поделился своим решением. Допустим, если сначала на страницу зашли с мобильного устройства, она закэшируется и количество запросов к БД и время генерации спадёт, но только для тех, кто будет заходить с мобильного устройства. Если человек будет заходить с настольного компьютера на эту же страницу, то количество обращений к БД и время генерации страницы спадать не будет. По крайней мере, именно эта печальная ситуация обнаружилась на первом подопытном.