Доброго здравия всем хабражителям!
MODx Revolution удобна во многих отношениях. Если в MODx Evolution можно было сделать всё, то в MODx Revolution можно сделать абсолютно всё. Были бы фантазия и терпение. Однако, после появления Revolution у многих встал вопрос: каким образом перетащить содержимое с одного движка на другой. Одно дело, если у Вас с десяток ресурсов. Тут копипаста Вам в помощь. Другое дело — коллекции контента, каталоги и прочее подобное.
Предыстория
Было у меня две коллекции — анекдотник и былинник. В первой я собирал любимые анекдоты, во второй — истории с «ЯПлакалъ», «IThappens» и прочих интересных порталов. Всё это висело на Evolution 1.0.5. Однако, в один прекрасный день я перевёл весь свой много-доменный сайт на один движок и одну БД. В общем перешёл на Revolution. Естественно встал вопрос о переносе контента. С разделом «о себе» и музыкальным разделом всё было просто — копипаста. О форуме я вообще не парился — он всё равно на phpBB. А вот с анекдотником и былинником вопрос пришлось отложить в долгий ящик, ибо скопипастить всё накопившееся там терпения не хватило бы…
Экспорт
На старом сайте жил малюсенький сниппет импорта случайного анекдота с анекдотника. По сути анекдотник мог экспортировать данные. В последствии я сделал специальную страничку, которая экспортировала всё содержимое сайта в формат JSON, да и забыл про неё. Когда встал вопрос о переносе данных вспомнил именно о ней.
Почему JSON? Да просто, наверное, потому, что устал я чертовски от всяких XML-парсеров. Даром, что для JSON существует простейшие функции — json_encode и json_decode. Это чрезвычайно удобное обстоятельство делает вариант с JSON куда более предпочтительнее, чем все остальные варианты.
С экспортом в JSON всё просто. Итак содержимое страницы для экспорта (шаблон _blank):
{"items":[ [[Ditto? &startID=`162` &tpl=`cat` &tplLast=`catLast`]] ]}
Содержимое чанка cat:
{ "name":"[+pagetitle+]", "alias":"[+alias+]", "template":"[+template+]", "hidemenu":"[+hidemenu+]", "content":[ [!Ditto? &startID=`[+id+]` &tpl=`item` &tplLast=`itemLast`!] ] },
catLast — то же самое, только без запятой в конце. Содержимое чанка item:
{ "name":"[+pagetitle+]", "alias":"[+alias+]", "template":"[+template+]", "hidemenu":"[+hidemenu+]", "content":"[+content:strip:noquotes+]" },
itemLast — то же самое, только без запятой в конце.
В итоге получается внушительный такой файлик. Да, главное — не забыть выставить тип данных на странице экспорта. Тип данных — text/javascript. Каким-то макакером можно сразу экспортировать данные Ditto в JSON. Но времени разбираться в этом вопросе не было и нет.
Импорт
Файлик получили. Что дальше? А дальше я наткнулся на статейку о создании соц-сети на MODx и узрел, как именно программным путём можно создать в MODx Revolution новые документы. Родилась идея, а вслед за ней сниппет:
<?php // Импорт из JSON-файла // Функция отвечающая за добавление ресурса function addItem($ctx,$pagetitle,$template,$isfolder,$hidemenu,$parent,$alias,$content,$td){ global $modx; $newResource = $modx->newObject('modResource'); $newResource->fromArray(array( 'pagetitle'=>$pagetitle, 'longtitle'=>$pagetitle, 'content'=>$content, 'template'=>$template, 'isfolder'=>$isfolder, 'hidemenu'=>$hidemenu, 'parent'=>$parent, 'published'=>'1', 'alias'=>$alias, 'context_key'=>$ctx )); if ($newResource->save()) { $id = $newResource->get('id'); $modx->cacheManager->refresh(); $modx->reloadConfig(); if (is_array($td)) { foreach($td as $key=>$val) { $tvar = $modx->newObject('modTemplateVarResource'); $tvar->set('contentid',$id); $tvar->set('tmplvarid',$key); $tvar->set('value',$val); $tvar->save(); } } return $id; } else { return false; } } // Функция, отвечающая за рекурсивную обработку массива с данными function handleItem($ctx,$item,$parent,$tpls,$tvs,$handleChildren=false){ $hidm = isset($item['hidemenu'])?$item['hidemenu']:'0'; $isf = is_array($item['content'])?'1':'0'; $content = is_array($item['content'])?'':$item['content']; $tpl = array_key_exists('tpl'.$item['template'],$tpls)?$tpls['tpl'.$item['template']]:'0'; $td = array(); foreach($tvs as $tvn=>$tvv) if (array_key_exists($tvn,$item)) $td[$tvv] = $item[$tvn]; $ret = ''; if ($id = addItem($ctx,$item['name'],$tpl,$isf,$hidm,$parent,$item['alias'],$content,$td)) { $ret = 'Resource «<b>'.$item['name'].'</b>» imported successfully! ' . 'New ID: <b>'.$id.'</b><br />'; if (is_array($item['content']) && $handleChildren) foreach ($item['content'] as $i) $ret.= handleItem($ctx,$i,$id,$tpls,$tvs,$handleChildren); return $ret; } else { return 'Resource «<b>'.$item['name'].'</b>» not imported!<br />'; } } // Шапкама лога $cons = '<h1>Import item log</h1>'; // Количество импортируемых за один раз элементов (для не сильно производительных систем) $item_count = isset($itemCount)?$itemCount:4; // Контекст, куда всё импортируется if (!isset($curContext)) $curContext = 'web'; // "Запоминалка" следующих элементов к импорту (для не сильно производительных систем) $next_items = isset($_GET['jsonimportnext'])?intval($_GET['jsonimportnext']):0; // Сопоставление шаблонов $tpls = array(); if (isset($templates)) { $tmp = explode(',',$templates); foreach($tmp as $val) { $tpls_d = explode('=>',$val); $tpls['tpl'.$tpls_d[0]] = $tpls_d[1]; } } // Сопоставление TV-параметров $tvs = array(); if (isset($tvParams)) { $tmp = explode(',',$tvParams); foreach($tmp as $val) { $tvs_d = explode('=>',$val); $tvs[$tvs_d[0]] = $tvs_d[1]; } } // Сам процесс if (isset($source) && isset($rootID)) { if ($import_content = @file_get_contents($source)) { $import_data = json_decode($import_content,true); $import_count = count($import_data['items']); if ($item_count != 0) { for($c = 0; $c < $item_count; $c++) { $n = $item_count*$next_items+$c; if (isset($import_data['items'][$n])) $cons.= handleItem($curContext,$import_data['items'][$n],$rootID,$tpls,$tvs); } $this_res = $modx->resource->get('alias'); $this_res.= '.html'; if (($item_count*$next_items+$item_count-1)<$import_count) { $cons.= '<br /><a href="'.$this_res.'?jsonimportnext=' . ($next_items+1).'">' . 'Import next items</a><br />'; } else { $cons.= '<br /><a href="'.$this_res.'">Start</a>'; } } else { foreach ($import_data['items'] as $item) $cons.= handleItem($curContext,$item,$rootID,$tpls,$tvs,true); } } else { $cons.= 'Cannot get source!<br />'; } } else { $cons.= 'Invalid execution parameters!<br />'; } return $cons;
Сразу скажу: это не претендует на универсальное решение. Код практически не откомментирован, увы. Слишком спешил поделиться с нуждающимися. Если решение покажется интересным, буду продолжать развивать работу и возможно создам полноценную надстройку над MODx.
На вход сниппет получает следующие параметры:
- source (обязательно) — источник JSON файла.
- itemCount — количество импортируемых элементов за один проход (для не сильно производительных систем). По умолчанию — 4. Если выставить в 0, обрабатываться будет всё за один раз, притом рекурсивно.
- templates — сопоставление шаблонов. Через запятую перечисляются сопоставления в формате old_id=>new_id, где old_id — id шаблона старого сайта, new_id — id шаблона нового сайта. Если парсер не находит сопоставления, выставляется шаблон 0 (пустой).
- tvParams — сопоставление TV-параметров. Через запятую перечисляются сопоставления в формате old_name=>new_id, где old_name — имя переменной старого сайта, new_id — id переменной нового сайта. Если парсер не находит сопоставления, переменная пропускается.
- curContext (обязательно) — текущий контекст. В принципе, если не выставить контекст, тогда он установится в «web».
- rootID (обязательно) — id ресурса, куда будут импортироваться документы.
К чему разговоры о производительности. А дело в том, что когда я запустил ещё первую версию сниппета, где рекурсивно должно было обрабатываться всё, сервер мне выдал 502-ю ошибку. Проще говоря хостер зарубил высокую нагрузку. Ещё бы — там столько документов было.
Как пользоваться
Для начала пишем простенький шаблон:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru"><head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <base href="/" /> <title>[[*pagetitle]]</title> <style type="text/css"> body { font: 12px monospace; } </style> </head><body><div align="center"><div style="text-align: left; width: 800px;"> [[!importJSON? &source=`[[*sourceURL]]` &itemCount=`6` &templates=`[[*templatesReplace]]` &tvParams=`[[*tvsReplace]]` &curContext=`[[*currentContext]]` &rootID=`[[*importDestination]]`]] </div></div></body></html>
Затем создаём и привязываем к шаблону TV-параметры sourceURL, templatesReplace, tvsReplace, currentContext, importDestination. Не надо материться на currentContext и вещать мне про context_key. В теории Вы можете создать одну страницу и импортировать данные в разные контексты. Собственно всё. В дополнение скажу, как использовал эту штуку я. Сразу сделаю примечание, что я в шаблоне экспорта обходился без категорий, меняя каждый раз startID. Из-за ограничений по нагрузке. Последовательность моих действий.
- На старом сайте открываем на редактирование файл экспорта. Ставим на дальнейшее действие «продолжить».
- На новом сайте открываем на редактирование файл, куда мы переносим контент (далее файл импорта). Меняем шаблон на шаблон импорта из JSON, сохраняем.
- В параметрах файла импорта выставляем текущий контекст, URL файла экспорта, сопоставление шаблонов и TV-параметров. Сохраняем.
- В файле экспорта меняем значение в startID на id родительского ресурса, откуда будем экспортировать контент. Сохраняем.
- В файле импорта выставляем id ресурса, куда будем импортировать. Сохраняем.
- Вызываем файл импорта на просмотр. Далее повторяем, пока в конце не появится ссылка с надписью «Start»:
- Ждём, пока загрузка завершится.
- Нажимаем на ссылку «Import next items»
- После того, как импортировали всё из нужного ресурса, возвращаемся к пункту 4, если ещё что-то нужно импортировать.
Да, знаю, для большей производительности можно было бы делать всё прямыми запросами к БД. Только, во-первых, не факт, что это исправило бы ситуацию с 502-й ошибкой. Во-вторых, не было времени изучать что затрагивается в БД при создании ресурса, кроме site_content. В-третьих, написал бы такое решение, меня бы тут же запинали бы с формулировкой «а-как-же-XPDO».
Ещё раз напоминаю, что это лишь предварительный набросок решения. Всем спасибо за внимание к моему очередному велосипеду!
ссылка на оригинал статьи http://habrahabr.ru/post/157483/
Добавить комментарий