Автосоздание ресурсов в контекстах-дублях
Задача:
Один сайт, несколько городов, один и тот же контент у всех, но разные данные (переменные).
Поначалу думал использовать Babel, но с ним не все так просто. Поэтому пришлось писать самому при поддержке неравнодушных Максима Кузнецова и Сергея Шлокова.
Решение:
1) Плагин для переключения контекстов отсюда bezumkin.ru/sections/tips_and_tricks/2439/ (Василию респект). Работаем на поддоменах. По настройке все брал со статьи Василия.
2) Плагин для работы с ресурсами в контекстах-дублях (создание, редактирование, удаление, восттановление):
Не забываем, плагин должен реагировать на события OnDocFormSave, OnDocFormDelete.
3) Снипет вывода контекстов как меню
4) К нему обязателен чанк tpl.context_menu:
5) Снипет для подтягивания контента из ресурсов оригинального контекста. Можно использовать pdoField опять же от Василия Наумкина, но не было желания ставить излюбленый «швейцарский нож» для открытия банки Колы )).
6) JS-скрипт для переключения контекстов:
Один сайт, несколько городов, один и тот же контент у всех, но разные данные (переменные).
Поначалу думал использовать Babel, но с ним не все так просто. Поэтому пришлось писать самому при поддержке неравнодушных Максима Кузнецова и Сергея Шлокова.
Решение:
- Плагин для переключения контекстов (от Василия Наумкина)
- Плагин на события: OnDocFormSave, OnDocFormDelete.
- Снипет для вывода контекстов в качестве меню
- Чанк вывода меню контекстов
- Снипет для подтягивания полей ресурсов-оригиналов (чтоб не дублировать в базу весь контент к примеру)
- Js-скрипт для переключения контекстов
1) Плагин для переключения контекстов отсюда bezumkin.ru/sections/tips_and_tricks/2439/ (Василию респект). Работаем на поддоменах. По настройке все брал со статьи Василия.
2) Плагин для работы с ресурсами в контекстах-дублях (создание, редактирование, удаление, восттановление):
<?php
if ($modx->event->name == 'OnDocFormSave') {
// собираем дублированные контексты (web - оригинальный контекст)
$contexts = $modx->getCollection('modContext', array('key:NOT IN' => ['mgr','web']));
// получаем родителя создаваемого ресурса
$parent = $resource->get('parent');
if ($parent != '0') {
$parentId = $modx->getObject('modResource', $parent);
$parentAlias = $parentId->get('alias');
}
$alias = $resource->get('alias');
$id = $resource->get('id');
// проходимся по контекстам
foreach ($contexts as $context) {
$response = $modx->getObject('modResource', array('context_key'=>$context->key, 'alias' => $alias));
// если ресурс уже существует то тогда просто обновляем поля
if ($response) {
$response->set('pagetitle', $resource->get('pagetitle'));
$response->set('content', '[[!OriginalFields?&id=`'.$id.'`&field=`content`]]');
$response->set('alias', $resource->get('alias'));
$response->set('deleted', $resource->get('deleted'));
//... еще много полей ресурса
$response->setTVValue('title', $resource->getTvValue('title'));
//... еще различные tv-шки
$response->save();
// если ресурса в контексте нет то создаем новый
} else {
// создание нового ресурса
$newResource = $modx->newObject('modDocument');
// заполняем поля ресурса
$newResource->set('context_key', $context->key);
$newResource->set('pagetitle', $resource->get('pagetitle'));
$newResource->set('content', '[[!OriginalFields?&id=`'.$id.'`&field=`content`]]');
$newResource->set('alias', $resource->get('alias'));
$newResource->set('deleted', $resource->get('deleted'));
//... еще много полей ресурса
// поле родителя для создания дубля в нем а не в корне
if ($parent != '0') {
$res = $modx->getObject('modResource', array('context_key'=>$context->key, 'alias'=>$parentAlias));
$parntId = $res->get('id');
} else {
$parntId = $parent;
}
$newResource->set('parent', $parntId);
$newResource->save();
// получаем id свежесозданного ресурса
$docId = $newResource->get('id');
// и заполняем различными tv-шками
$tvs = $modx->getObject('modResource', $docId);
//... еще различные tv-шки
$tvs->save();
}
}
// очищаем кеш
$modx->cacheManager->clearCache();
}
// удаление ресурсов в дублирующих контекстах
if ($modx->event->name == 'OnDocFormDelete') {
// собираем дублированные контексты (web - оригинальный контекст)
$contexts = $modx->getCollection('modContext', array('key:NOT IN' => ['mgr','web']));
// проходимся по контекстам
foreach ($contexts as $context) {
// получаем нужные (верней не нужные поэтому и удаляемые) нам ресурсы
$response = $modx->getObject('modResource', array('context_key'=>$context->key, 'alias' => $resource->get('alias')));
// помечаем как удаленные
$response->set('deleted', $resource->get('deleted'));
$response->save();
// удаляет полностью
// $response->get('id');
// $response->remove();
}
$modx->cacheManager->clearCache();
}
Вообще было бы идеально сделать заполнение/обновление полей ресурса через перебор вроде foreach, но пока руки остановились на этом.Не забываем, плагин должен реагировать на события OnDocFormSave, OnDocFormDelete.
3) Снипет вывода контекстов как меню
<?php
$contexts = $modx->getCollection('modContext', array('key:NOT IN' => ['mgr']));
$tpl = $modx->getOption('tpl',$scriptProperties , 'tpl.context_menu');
foreach ($contexts as $context) {
$ctxArray = $context->toArray();
$output .= $modx->getChunk($tpl,$ctxArray);
}
return $output;
4) К нему обязателен чанк tpl.context_menu:
<li class="js-context_key [[+key]] [[*context_key:is=`[[+key]]`:then=`active`:else=``]]" data-key="[[+key]]" data-link="[[++site_url]]">[[+name]]</li>
5) Снипет для подтягивания контента из ресурсов оригинального контекста. Можно использовать pdoField опять же от Василия Наумкина, но не было желания ставить излюбленый «швейцарский нож» для открытия банки Колы )).
$res = $modx->getObject('modResource', $id);
$content = $res->get($field);
return $content;
Снипет вызываем так: [[snippet?&id=`[[*id]]`&field=`content`]]6) JS-скрипт для переключения контекстов:
$(document).ready(function(){
// установка у актуального города класса active
$(function(){
var city = $('#city_menu'),
city_key = city.data('context'),
city_active = city.find('.js-context_key.'+city_key);
city_active.addClass('active');
});
// настройка и переход на страницы других контентов
$('.js-context_key').click(function(){
var link = $(this),
key = link.data('key'),
locey = window.location.href,
context = link.parent().data('context'),
newlocey;
if ( context == 'web' ) {
if ( locey.indexOf("https") === true ) {
console.log('https');
newlocey = locey.replace('https://', 'https://'+key+'.');
} else {
console.log('http');
newlocey = locey.replace('http://', 'http://'+key+'.');
}
} else {
if ( key == 'web' ) {
newlocey = locey.replace(context+'.', '');
} else {
newlocey = locey.replace(context, key);
}
}
console.log(key+' - key; '+locey+' - locey; '+context+' - context; '+newlocey+' - locey;');
if ( context != key ) {
location.href = newlocey;
}
});
});
Ну и сама html разметка:<div id="citys_menu">
<ul id="city_menu" data-context="[[!*context_key]]">
[[!ContextMenu?]]
</ul>
</div>
У данного способа есть недостатки:- например что не получается, так это восстановить ресурс после помечания на удаление через всплывающее меню (правый клик). Приходиться вызвать быстрое редактирование и там убирая галочку сохранять.
- переключение работает на js а хотелось бы на php, если вдруг js отключат из-за нехватки ресурсов, кризис на дворе ))
Комментарии: 23
$response->set('content', '[[!OriginalFields?&id=`'.$id.'`&field=`content`]]');
— выглядит как какое-то извращение..) Лучше так:
$params = array();
$params['id'] = $id;
$params['field'] = 'content';
$snippet_result = $modx->runSnippet('OriginalFields', $params);
$response->set('content', $snippet_result);
UPD: А для чего эта магия, если не секрет?
$id = $id;
$field = $field;
По поводу текста контента, весь фокус в том чтобы не вставлять весь контент в ресурсы контекстов-дублей. Представь что у оригинального контекста статься на 5000 знаков и это все помнож на количество контекстов-дублей (к примеру 80) иного 400000 знаков в базе зачем они там? не лучше ли оставить 5000 у ресурса оригинального контекста, а в поле контекста ресурсов-дублей поставить вызов снипета который и будет вытягивать контент только оригинального ресурса. надеюсь понятно )).
По поводу
$id = $id;
$field = $field;
лучше у Копирфильда спросить я и сам пока разбираюсь. Вобще тут должны подтягиваться настройки снипета для подтягивания контента из ресурсов оригинального контекста (пункт 5). к примеру [[originalFields?&id=`[[*id]]`&field=`content`]]. Я учусь и буду только рад если кто подскажет как это правильно подтянуть в самом снипете.
По поводу
$id = $id;
$field = $field;
лучше у Копирфильда спросить я и сам пока разбираюсь. Вобще тут должны подтягиваться настройки снипета для подтягивания контента из ресурсов оригинального контекста (пункт 5). к примеру [[originalFields?&id=`[[*id]]`&field=`content`]]. Я учусь и буду только рад если кто подскажет как это правильно подтянуть в самом снипете.
Первая часть кода задавалась вопросом не что делает этот сниппет и зачем он нужен, а лишь о способе его вызова в php-коде.
По поводу переменных для самописного сниппета — все параметры вида ¶m=`value` по-умолчанию доступны внутри сниппета в виде переменных $param.
По поводу переменных для самописного сниппета — все параметры вида ¶m=`value` по-умолчанию доступны внутри сниппета в виде переменных $param.
в том то и дело что в этом плагине он ничего не должен делать, а то он просто подставит весь контент в ресус-дубь. чего и хотелось избежать.
то есть код
то есть код
[[!OriginalFields?&id=`'.$id.'`&field=`content`]]
должен сработать не в момент работы плагина по редактированию ресурса, а в момент открытия страницы. поэтому от и вынесен в отдельный снипет. в противном случае можно было бы просто указать:$response->set('content', $resource->get('content'));
… если честно, не совсем понимаю, о каком открытии страницы идет речь.
Например, для такого вызова плагина ваш сниппет сможет получить данные?
Например, для такого вызова плагина ваш сниппет сможет получить данные?
$modx->invokeEvent('OnDocFormSave',array(
'mode' => 'upd',
'id' => $page->get('id'),
'resource' => &$page,
));
про «открытие страницы» все просто:
оригинал — joxi.ru/4AkOW64upM6nAq
дубль — joxi.ru/1A598YPcaKezAE
про ваш плагин, я вобщето еще был бы рад подучить то что вы тут написали. маловато знаний у меня, только учусь. если объясните что и как, отвечу.
оригинал — joxi.ru/4AkOW64upM6nAq
дубль — joxi.ru/1A598YPcaKezAE
про ваш плагин, я вобщето еще был бы рад подучить то что вы тут написали. маловато знаний у меня, только учусь. если объясните что и как, отвечу.
Если у вас есть единый контент для всех связанных ресуров, содержимое которого хранится у ресурса web-контекста, можно поступить так:
Далее, уже при сохранении, обновляем содержимое основного ресурса и очищаем контент текущего:
if ($modx->event->name == 'OnBeforeDocFormSave' && $mode == 'upd') {
if ($resource->get('context') != 'web') {
$original_page = $modx->getObject('modResource', array(
'context_key'=> 'web',
'alias' => $resource->get('alias')
));
if ($original_page) {
$resource->set('content', $original_page->get('content'));
}
}
}
— таким способом мы будем подставлять контент основного ресурса при инициализации страницы редактирования побочных. Далее, уже при сохранении, обновляем содержимое основного ресурса и очищаем контент текущего:
if ($modx->event->name == 'OnDocFormSave' && $mode == 'upd') {
if ($resource->get('context') != 'web' && strlen($resource->get('content')) > 0) {
$original_page = $modx->getObject('modResource', array(
'context_key'=> 'web',
'alias' => $resource->get('alias')
));
if ($original_page) {
$original_page->set('content', $resource->get('content'));
$resource->set('content', '')
}
}
}
не вижу в этом смысла…
во первых плагин должен выполнить дополнительные манипуляции, что увеличивает время работы (а контекстов у нас уйма).
во вторых зачем забивать в базу дублями контента, который может быть довольно обьемный.
во первых плагин должен выполнить дополнительные манипуляции, что увеличивает время работы (а контекстов у нас уйма).
во вторых зачем забивать в базу дублями контента, который может быть довольно обьемный.
Понял. Ну, плагин выше и не будет плодить в базу дубли контента, он по-прежнему будет один у web'a — у остальных же поле будет очищаться при сохранении.
Вышеописанный код решает задачу, с помощью которой можно видеть в админке любого дублируемого ресурса оригинальный контент и работать с ним напрямую.
А сниппет, на мой взгляд, уместнее вынести непосредственно в шаблон:
Вышеописанный код решает задачу, с помощью которой можно видеть в админке любого дублируемого ресурса оригинальный контент и работать с ним напрямую.
А сниппет, на мой взгляд, уместнее вынести непосредственно в шаблон:
[[*context_key:is=`web`:then=`[[*content]]`:else=`[[!OriginalFields?&id=`[[*original_id]]`&field=`content`]]`]]
у меня не было задачи чтобы с полями ресурсов-дублей велась работа. туда вообще бы никогда бы и не заглядывали. Но ваше решение интересно… надо будет влить в плагин.
И уже далее вы правы на счет вывода синпета в шаблон. Но это уже за гранью того что должен был реализовать. Вам огромное спасибо, что помогли.
И уже далее вы правы на счет вывода синпета в шаблон. Но это уже за гранью того что должен был реализовать. Вам огромное спасибо, что помогли.
Да не за что..)
Видимо, я все-таки не совсем корректно понял задачу — мне казалось, что ресурсы дублируются во все контексты, т.к. у них потенциально могут быть разные подданные для каждого города. Если же у вас все данные одинаковые и редактировать страницы-дубли никто не будет, то правильнее вообще было бы не плодить лишних страниц, а воспользоваться кастомной маршрутизацией, которая перехватывала бы событие OnPageNotFound и отображала бы страницу-оригинал, доступную по адресу с идентификатором города.
Видимо, я все-таки не совсем корректно понял задачу — мне казалось, что ресурсы дублируются во все контексты, т.к. у них потенциально могут быть разные подданные для каждого города. Если же у вас все данные одинаковые и редактировать страницы-дубли никто не будет, то правильнее вообще было бы не плодить лишних страниц, а воспользоваться кастомной маршрутизацией, которая перехватывала бы событие OnPageNotFound и отображала бы страницу-оригинал, доступную по адресу с идентификатором города.
Интересно, нужно будет на днях изучить и понять. Спасибо.
Вобще то вы правильно поняли задачу. Поэтому и пришлось делать на контекстах, чтобы в их настройках хранить данные для каждого города отдельно. Дальше бы они подставлялись в контент или в шапку или в контакты… типа:
Наш город [[++city]] самый лучший на планете. В нем живут [[++people]] жителей.
Наш город [[++city]] самый лучший на планете. В нем живут [[++people]] жителей.
К слову, о востребованности сниппета — не лучше ли вне цикла получать поле content у основного ресурса один раз, после чего просто передавать переменную?
//...
$original_content = $resource->get('content');
//...
foreach ($contexts as $context) {
//...
$response->set('content', $original_content);
}
— в таком варианте вы запросите содержимое контента один раз, а не столько, сколько у вас контентов, отличных от web/mgr.
А что не так с babel, поделитесь пожалуйста? А то у меня тоже начинается тут один мультиязычный проект. Раньше babel не использовал, поставил-попробовал, вроде нормально работает.
Для бабела нужна привязка ресурс-ресурс для перелинковки. В моей задаче не должно было быть действий от заказчика вроде «создать перевод ресурса 325», то есть ничего ручками кроме тех преславутых переменных в контенте и по сайту
для простого мультиязычника бабел вполне хорошая весч.
для простого мультиязычника бабел вполне хорошая весч.
Спасибо.
Молодец! Интересное решение! Есть полезные моменты, спасибо что выложил…
Синхронизация всего со всем (документы, ТВшки и прочее) в разных контекстах — дикая заморочка. Потом начнется где-то в одном документе отредактировал, а в других не поменялось и т.д. и т.п. Да и при реальном каталоге в 10 000 документов при 10 контекстах иметь 100 000 документов — не очень интересно.
Прочитайте вот этот комментарий, может идеи возникнут.
P.S. Я бы так не стал делать, слишком много тонких моментов.
Прочитайте вот этот комментарий, может идеи возникнут.
P.S. Я бы так не стал делать, слишком много тонких моментов.
Спасибо Николай. Я был бы счастлив пройти курс «молодого бойца» модэкс, но где его преподают. Документация на английском, его еще выучить надо. А пока учу английский и мечтаю о курсе пишу исходя из знаний, дабы было что на хлеб намазать )). Если можете подсказать где и как выучить модэкс вдоль и поперек, буду признателен. Мне предлагали перейти на другие движки, но уж нет. С этого мерседеса (не реклама) я не слезу.
Не за что.
Юрий, я не знаю такого курса. Но информации много и здесь, и у нас на сайте modxclub.ru, и на других ресурсах. Другое дело, что этой информации очень много, и все равно только в процессе работы будет приходить необходимый опыт. Я этот свой комментарий оставил просто для того, чтобы видели, что есть и другие рабочие механизмы в MODX, которые лучше подходят для реализации подобных задач, чтобы знали в сторону чего копать. Но четкого сценария нет, надо в любом случае изучать все это.
Юрий, я не знаю такого курса. Но информации много и здесь, и у нас на сайте modxclub.ru, и на других ресурсах. Другое дело, что этой информации очень много, и все равно только в процессе работы будет приходить необходимый опыт. Я этот свой комментарий оставил просто для того, чтобы видели, что есть и другие рабочие механизмы в MODX, которые лучше подходят для реализации подобных задач, чтобы знали в сторону чего копать. Но четкого сценария нет, надо в любом случае изучать все это.
Добрый день
Вывел контексты в виде меню, но почему то не получается их отсортировать по rank. Контексты отсортированы по key, а это не очень удобно. У меня несколько контекстов-городов. За контекст с названием Москва отвечает web а он получается в самом конце.
Вывел контексты в виде меню, но почему то не получается их отсортировать по rank. Контексты отсортированы по key, а это не очень удобно. У меня несколько контекстов-городов. За контекст с названием Москва отвечает web а он получается в самом конце.
Подскажите пожалуйста как вывести id родителя, основного контекста? Необходимо для ms2gallery &resources=``
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.