[СДЕЛАЙ САМ] Контексты для регионов в интернет-магазине.
Задача: сделать максимально простое добавление новых контекстов на сайт для создания региональных копий с собственными ресурсами, robots.txt и sitemap.xml, уникальными для каждого региона ресурсами и ценами на некоторые товарные позиции.
Проблемы:
1. Вывод галереи изображений товаров
2. Добавление дополнительных категорий к товарам в новом контексте.
3. Связывание товаров.
Решение:
1. Настраиваем редирект со всех поддоменов на основной домен (как это сделать уточняйте у хостера или в Google);
2. Подключаем плагин для переключения контекстов:
Логика такая: определяем по url какой контекст запросили, если это не основной контекст (web), то переключаем контекст на запрошенный, в противном случае смотрим есть ли в $_COOKIE город, если нет, то устанавливаем $_COOKIE['curCity'], проверяем есть ли в списке контекстов запрошенный, если есть, то переключаем, если нет остаемся на основном контексте. На фронте этим управляет вот такой код
2.1 если кому интересно город я определяю через сайт DaData.Код сниппета detectRegion тут.
3. Подключаем плагин для создания настроек контекста, при копировании или создании нового.
Плагин рассчитан на работу с поддоменами. Но можно скорректировать и для работы с другим форматом url.
В процессе работы всплыл один неприятный нюанс, а именно: дополнительные категории у товаров не копировались, что неудивительно, но нужно было что-то делать. Поскольку при копировании контекста указывается только новый ключ и потом всё равно приходится заходить и прописывать его название, то я решил что модно использовать для запуска копирования дополнительных категорий ужа написанный плагин и использовал для этого сниппет setAddProductCategories
С 5 сотнями товаров и 3 сотнями записей в таблице modx_ms2_product_categories работает нормально.
Ещё очень важный момент — для всех шаблонов нужно создать TV linked_id куда для контекста web записать id текущего ресурса, для других контекстов id исходного ресурса. Например у вас есть контекст wtb с ресурсом Главная у которого id = 1. Если вы других ресурсов не создавали, то при копировании контекста с ресурсами в новом контексте назовем его new появится ресурс Главная c id = 2. Так вот в linked_id у ресурса с id =1 должно быть 1, и у ресурса с id=2 тоже 1. Такой подход даёт возможность не копировать галерею изображений товаров, а вызывать её из контекста web. Для этого нужен сниппет getLinkedResources, который на вход принимает строку из id разделенных запятой, а возвращает другую строку из связанных id разделенных запятой, если вернуться к примеру выше, то получив на вход 2 он вернёт 1.
Из кода видно что можно использовать как модификатор. Кейс применения такой. В контексте web есть ресурс у которого есть TV со списком id других ресурсов, в моём случае это были примеры работ, при копировании контекста эта TV копируется вместе со значениями, так вот чтобы не перезаполнять, я скармливают сниппету getLinkedResources то, что записано в этой TV, и получаю список id из нужного мне контекста. Пример вызова
И наверное последнее о чём стоит упомянуть это вывод списка городов, я для этого написал отдельный сниппет getAllContexts
Тут важно продублировать в настройках контекста web настройку site_url слеш в конце обязателен и прописать ctx_city. После этого можно выводить список городов вот так
Важно чтобы у всех связанных страниц был одинаковый псевдоним, это необходимо для того, чтобы при смене города в процессе просмотра сайта пользователь попадал не на главную страницу другого контекста, на такую же.
Кажется ничего не забыл. Спасибо за внимание. Всё.
Проблемы:
1. Вывод галереи изображений товаров
2. Добавление дополнительных категорий к товарам в новом контексте.
3. Связывание товаров.
Решение:
1. Настраиваем редирект со всех поддоменов на основной домен (как это сделать уточняйте у хостера или в Google);
2. Подключаем плагин для переключения контекстов:
<?php
// Работаем только на фронтенде
if ($modx->event->name != 'OnHandleRequest' || $modx->context->key == 'mgr') {return;}
// Определяем запрашиваемый хост
$host = $_SERVER['HTTP_HOST'];
$ctx = $modx->getObject('modContextSetting', array('key' => 'http_host', 'value' => $host));
if($ctx->get('context_key') != 'web'){
$modx->switchContext($ctx->get('context_key'));
}Логика такая: определяем по url какой контекст запросили, если это не основной контекст (web), то переключаем контекст на запрошенный, в противном случае смотрим есть ли в $_COOKIE город, если нет, то устанавливаем $_COOKIE['curCity'], проверяем есть ли в списке контекстов запрошенный, если есть, то переключаем, если нет остаемся на основном контексте. На фронте этим управляет вот такой код
let btns = document.querySelectorAll('.jsChooseBtn'), // это кнопки подтверждения ДА и НЕТ
tooltip = document.querySelector('.jsCityTooltip'); // это само окно с вопросом "ЭТО ВАШ ГОРОД"
for(let i = 0; i < btns.length; i++){
btns[i].addEventListener('click', function(e){
tooltip.classList.add('d-none');
if(e.target.classList.contains('jsCityConfirm')){ // если нажали да
document.cookie = 'curCity='+ e.target.dataset.city + '; path=/;domain=ecodecking.ru';
}
});
}
if(document.cookie.indexOf('curCity') != -1){ // проверяем есть ли город в куках
tooltip.classList.add('d-none');
} 2.1 если кому интересно город я определяю через сайт DaData.Код сниппета detectRegion тут.
<?php
$url = 'https://suggestions.dadata.ru/suggestions/api/4_1/rs/iplocate/address';
$siteIsAvaliable = $modx->runSnippet('isSiteAvailible', array('url' => $url));
if($siteIsAvaliable){
$token = $modx->getOption('dadata_api_key');
$ip = $_SERVER['REMOTE_ADDR'];
$headers = array(
'Accept: application/json',
'Authorization: Token ' . $token
);
$ch = curl_init($url.'?ip='.$ip);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);
$result = json_decode(curl_exec($ch),1);
curl_close($ch);
$region = $result['location']['data']['city'];
return $region;
}else{
$modx->log(1, 'detectRegion: Не возможно определить регион. Сервис DaData недоступен');
return false;
}3. Подключаем плагин для создания настроек контекста, при копировании или создании нового.
<?php
switch ($modx->event->name) {
case 'OnContextSave':
$context_key = $context->get('key');
if($mode == 'new'){
$sql = 'SELECT AUTO_INCREMENT FROM information_schema.tables WHERE TABLE_NAME = "modx_site_content"';
$statement = $modx->query($sql);
$next_ids = $statement->fetchAll(PDO::FETCH_COLUMN);
$site_start = $next_ids[0];
$settings = array(
array(
'context_key' => $context_key,
'key' => 'base_url',
'value' => '/',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_base_url',
'value' => 'Base URL',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
array(
'context_key' => $context_key,
'key' => 'error_page',
'value' =>$site_start,
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_error_page',
'value' => 'Page 404',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
array(
'context_key' => $context_key,
'key' => 'http_host',
'value' => $context_key .'.'. $modx->getOption('http_host'),
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_http_host',
'value' => 'Название домена',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
array(
'context_key' => $context_key,
'key' => 'site_start',
'value' => $site_start,
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_site_start',
'value' => 'Site start',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
array(
'context_key' => $context_key,
'key' => 'site_name',
'value' => $modx->getOption('site_name'),
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_site_name',
'value' => 'Site name',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
array(
'context_key' => $context_key,
'key' => 'site_url',
'value' => $modx->getOption('server_protocol').'://'.$context_key .'.'. $modx->getOption('http_host').'/',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_site_url',
'value' => 'Site URL',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
array(
'context_key' => $context_key,
'key' => 'site_status',
'value' => 1,
'xtype' => 'combo-boolean',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_site_status',
'value' => 'Сайт опубликован',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
array(
'context_key' => $context_key,
'key' => 'ctx_city',
'value' => '',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'core',
'dictionary' => array(
'name' => 'setting_ctx_city',
'value' => 'Город контекста',
'topic' => 'setting',
'namespace' => 'core',
'language' => 'ru',
'createdon' => date('Y-m-d H:i:s')
)
),
);
foreach ($settings as $k => $s){
if(!$modx->getObject('modContextSetting', array('key' => $s['key'], 'context_key' => $context_key))){
$new_setting = $modx->newObject('modContextSetting');
$new_setting->fromArray($s, '',true);
$new_setting->save();
}
if(!$modx->getObject('modLexiconEntry', array('name' => 'setting_'.$s['key']))){
$new_dictionary = $modx->newObject('modLexiconEntry');
$new_dictionary->fromArray($s['dictionary'],'',true);
$new_dictionary->save();
}
}
}
else{
$modx->runSnippet('setAddProductCategories', array('ctx' => $context_key));
}
break;
}Плагин рассчитан на работу с поддоменами. Но можно скорректировать и для работы с другим форматом url.
В процессе работы всплыл один неприятный нюанс, а именно: дополнительные категории у товаров не копировались, что неудивительно, но нужно было что-то делать. Поскольку при копировании контекста указывается только новый ключ и потом всё равно приходится заходить и прописывать его название, то я решил что модно использовать для запуска копирования дополнительных категорий ужа написанный плагин и использовал для этого сниппет setAddProductCategories
<?php
if($ctx && $ctx != 'web' && $ctx != 'mgr'){
$resources = $modx->getIterator('modResource', array('context_key' => $ctx, 'class_key' => 'msProduct'));
foreach($resources as $r){
$sql = "SELECT category_id FROM modx_ms2_product_categories WHERE product_id = ".$r->getTVValue('linked_id');
$statement = $modx->query($sql);
$addCats = $statement->fetchAll(PDO::FETCH_COLUMN);
if($addCats){
foreach ($addCats as $cat) {
$addCat = $modx->getObject('modResource', $cat);
if($addCat){
$newAddCat = $modx->getObject('modResource', array(
'context_key' => $ctx,
'class_key' => 'msCategory',
'pagetitle' => $addCat->get('pagetitle')
));
if($newAddCat){
$s = array(
'product_id' => $r->get('id'),
'category_id' => $newAddCat->get('id')
);
$dbAddCat = $modx->getObject('msCategoryMember', $s);
if(!$dbAddCat){
$dbAddCat = $modx->newObject('msCategoryMember');
$dbAddCat->fromArray($s, '',true);
$dbAddCat->save();
}
}
}
}
}
}
}С 5 сотнями товаров и 3 сотнями записей в таблице modx_ms2_product_categories работает нормально.
Ещё очень важный момент — для всех шаблонов нужно создать TV linked_id куда для контекста web записать id текущего ресурса, для других контекстов id исходного ресурса. Например у вас есть контекст wtb с ресурсом Главная у которого id = 1. Если вы других ресурсов не создавали, то при копировании контекста с ресурсами в новом контексте назовем его new появится ресурс Главная c id = 2. Так вот в linked_id у ресурса с id =1 должно быть 1, и у ресурса с id=2 тоже 1. Такой подход даёт возможность не копировать галерею изображений товаров, а вызывать её из контекста web. Для этого нужен сниппет getLinkedResources, который на вход принимает строку из id разделенных запятой, а возвращает другую строку из связанных id разделенных запятой, если вернуться к примеру выше, то получив на вход 2 он вернёт 1.
<?php
if($input){
$ctx = $options ?: 'web';
$output = array();
$sql = "SELECT contentid FROM modx_site_tmplvar_contentvalues WHERE tmplvarid = 176 AND value IN (".$input .")";
$statement = $modx->query($sql);
$ids = $statement->fetchAll(PDO::FETCH_COLUMN);
$sql = 'SELECT id FROM modx_site_content WHERE id IN ('.implode(',',$ids).') AND context_key = "'.$ctx.'"';
$statement = $modx->query($sql);
$output = $statement->fetchAll(PDO::FETCH_COLUMN);
return implode(',', $output);
}Из кода видно что можно использовать как модификатор. Кейс применения такой. В контексте web есть ресурс у которого есть TV со списком id других ресурсов, в моём случае это были примеры работ, при копировании контекста эта TV копируется вместе со значениями, так вот чтобы не перезаполнять, я скармливают сниппету getLinkedResources то, что записано в этой TV, и получаю список id из нужного мне контекста. Пример вызова
{set $work_list = $_modx->resource.works_list | getLinkedResources : $_modx->context.key} И наверное последнее о чём стоит упомянуть это вывод списка городов, я для этого написал отдельный сниппет getAllContexts
<?php
$sql = 'SELECT `key`, `value`, `context_key` FROM modx_context_setting';
$statement = $modx->query($sql);
$settings = $statement->fetchAll(PDO::FETCH_ASSOC);
$output = array();
foreach($settings as $set){
if($set['context_key'] != 'mgr'){
$output[$set['context_key']][$set['key']] = $set['value'];
}
}
return $output;Тут важно продублировать в настройках контекста web настройку site_url слеш в конце обязателен и прописать ctx_city. После этого можно выводить список городов вот так
{set $settings = '!getAllContexts' | snippet}
{foreach $settings as $k => $set}
{if $k != $_modx->context.key}
{set $url = $set['site_url']}
{if $_modx->resource.alias != 'index'}
{set $alias = $_modx->resource.alias}
{/if}
<a href="{$url}{$alias}">{$set['ctx_city']}</a>
{/if}
{/foreach}Важно чтобы у всех связанных страниц был одинаковый псевдоним, это необходимо для того, чтобы при смене города в процессе просмотра сайта пользователь попадал не на главную страницу другого контекста, на такую же.
Кажется ничего не забыл. Спасибо за внимание. Всё.
Техническая поддержка MODX
Сайт лежит, тормозит или остался без разработчика?
Переезд с MODX 2 на 3, PHP 7 на 8, скорость и безопасность. Поддержка со сроками и ответственностью, а не совет в чате.
Подробнее
Реклама
0
Комментарии: 0