Перенос данных

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

Начиная с версии 2.8.3, в системе появилась возможность максимально полного переноса информации (например, отдельного модуля, со всеми необходимыми данными и файлами) из одной системы в другую.

Сейчас мы рассмотрим пример того, как можно получить все необходимые файлы и данные для переноса разработанного модуля (на примере модуля "Каталог") из девелоперской системы на сайт клиента. В этом примере мы будем создавать папку "export" для экспортируемых файлов и данных (в xml-формате), файл export.php для выполнения соответствующей операции, а также рассмотрим варианты организации импорта.

Получить наглядные примеры скриптов можно, скачав данный архив.

Начнём с файла export.php

В начале, как обычно, подготавливаем скрипт к автономной работе через API UMI.CMS:

<?php
 error_reporting(E_ALL);
 include "standalone.php";
 

Начинаем подготавливать данные для настройки экспорта. Во-первых, задаём (и создаём, в случае отсутствия) директорию для сохранения экспортированных файлов (вышеупомянутая папка "export"):

 $curr_dir = CURRENT_WORKING_DIR;
 $destinationPath = $curr_dir . "/export"; // Задаём директорию для сохранения файлов экспорта.
 if (!is_dir($destinationPath)) mkdir($destinationPath, 0777, true);

Создаём экземпляр экспорта и начинаем настройку:

 $exporter = new xmlExporter("catalog"); // Задаём идентификатор экспорта
 $exporter->setDestination($destinationPath); // Задаём директорию для сохранения файлов экспорта

В переменную "files" помещаем массив из файлов, которые необходимо экспортировать:

 /* Функция для рекурсивного выбора файлов из директорий */
 function recursiveGlob($pattern = '*', $flags = 0, $path = '') {
 	$paths = glob($path . '*', GLOB_MARK|GLOB_ONLYDIR|GLOB_NOSORT);
 	$files = glob($path . $pattern, $flags);
 	foreach ($paths as $path) { 
 		$files=array_merge($files, recursiveGlob($pattern, $flags, $path)); 
 	}
 	return $files;
 }
 $php_files = recursiveGlob('*', 0, $curr_dir . "/classes/modules/catalog/");
 $ico_files = array($curr_dir . '/images/cms/admin/mac/icons/big/catalog.png', 
  $curr_dir . '/images/cms/admin/mac/icons/medium/catalog.png', 
  $curr_dir . '/images/cms/admin/mac/icons/small/catalog.png');
 $tpl_files = recursiveGlob('*.tpl', 0, $curr_dir . "/tpls/catalog/");
 $xsl_files = recursiveGlob('*.xsl', 0, $curr_dir . "/xsltTpls/modules/catalog/");
 /* Выбираем файлы модуля */
 $files = array_merge($php_files, $ico_files, $tpl_files, $xsl_files);
 $exporter->addFiles($files); // Задаём массив экспортируемых файлов

В переменную "paths" собираем массив из ключей реестра. Для этого воспользуемся классом regedit. В нашем случае это будут ключи, необходимые для работы модуля (//modules/catalog/name, //modules/catalog/func_perms и т.д.):

 /*Функция для рекурсивного выбора ключей реестра*/
 function getAllRegistryList ($parent_path) {
 	$paths = array();
 	$children = regedit::getInstance()->getList($parent_path);
 	if (is_array($children)) {
 		foreach ($children as $child) {
 			$child_key = regedit::getInstance()->getKey($child[0]);
 		if ($parent_path != "//") {
 			$child_path = $parent_path . '/' . $child[0];
 		} else {
 			$child_path = $child[0];
 		}
 		$paths[] = $child_path;
 		$paths = array_merge($paths, getAllRegistryList($child_path));
 		}
 	}
 	return $paths;
 }
 /* Выбираем пути к записям реестра */
 $paths = getAllRegistryList ("//modules/catalog");
 /* Задаём массив путей к записям реестра */
 $exporter->addRegistry($paths);

Далее нам необходимо заполнить переменную "types" массивом из id типов данных, которые мы хотим экспортировать. Проще всего это сделать при помощи класса umiObjectTypesCollection:

 /* Например, так: */
 $typesCollection = umiObjectTypesCollection::getInstance();
 $typeId = $typesCollection->getBaseType('catalog', 'object');
 $types = $typesCollection->getSubTypesList($typeId);
 $types[] = $typeId;
 
 $exporter->addTypes($types);

Теперь, используя массив "types" и механизм Selector, мы сможем легко получить страницы(массив "pages") и объекты(массив "objects"), которые необходимо экспортировать:

 /* Выбираем страницы и объекты, относящиеся к выбранным выше типам данных */
 foreach ($types as $typeId) {
 	$sel = new selector("pages");
 	$sel->types('object-type')->id($typeId);
 	$pages = $sel->result();
 	$exporter->addElements($pages); // Задаём массив элементов
 
 	$sel = new selector("objects");
 	$sel->types('object-type')->id($typeId);
 	$objects = $sel->result();
 	$exporter->addObjects($objects); // Задаём массив объектов
 }

Так как при экспорте данных модуля нам обычно не нужны данные о домене и языке, мы исключаем их из экспорта при помощи метода setIgnoreRelations() класса "xmlExporter":

 $saveRelations = array('files', 'templates', 'objects', 'fields_relations', 'restrictions', 'permissions', 'hierarchy', 'guides');
 $exporter->setIgnoreRelations($saveRelations);

Завершаем настройку и запускаем экспорт:

 $exporter->setShowAllFields(true); // Устанавливаем флаг экспорта всех полей (в т.ч. системных и скрытых)
 $dom = $exporter->execute(); // Запускаем экспорт. Результат записываем в переменную
 $log = $exporter->getExportLog(); // В этой переменной хранится лог ошибок экспорта (нужна для отладки)

Осталось только сохранить полученные xml-данные (переменная "dom") в файл и можно работать:

 /* Сохраняем xml для будущего импорта */
 file_put_contents("./export/catalog.xml", $dom->saveXML());

На что следует обратить внимание во избежание проблем с экспортом:

 - При экспорте страниц и объектов, в директорию "destinationPath" будут также скопированы 
 файлы, связанные с этими элементами (например, изображения и файлы из соответствующих типов полей). 
 Поэтому:
    - Запускать скрипт-экспортер следует из корневой директории сайта. 
    Иначе путь к изображениям\файлам будет некорректным и они не будут скопированы.
 - Если при экспорте происходят ошибки - вывести массив с их описанием можно, например,
 строкой var_dump($log); в конце скрипта.
 - Если папка "destinationPath" создаётся вручную - убедитесь что для неё 
 выставлены права на запись скриптом.
 - Также стоит иметь в виду, что метод "setDestination" не создаёт директорию и 
 в случае её отсутствия (не создана вручную и не создаётся скриптом) экспорт файлов не будет произведен. 
 - Для корректной настройки экспорта настоятельно рекомендуем 
 ознакомиться с возможностями класса xmlExporter.

 

Итак, данные успешно экспортированы, мы получили папку "export" с необходимым содержимым и можем приступать к процессу импорта данных на другом сайте. Переместив директорию "export" в корень целевого сайта, мы имеем несколько вариантов для импорта. Так как они не слишком отличаются - основные опишем вместе:

1. Мы можем создать файл import.php в корне сайта и внести в него код, описанный ниже
2. Либо мы можем включить этот код в файл install.php самого модуля и получить все данные и файлы сразу при установке модуля.
В первом случае, в файле import.php нам нужно снова подключить standalone.php:

<?php
 error_reporting(E_ALL);
 include "standalone.php";
 $dir = CURRENT_WORKING_DIR . "/export";

Следующий код будет практически идентичен для обоих вариантов. Настраиваем и запускаем импортер:

 $importer = new xmlImporter();
 $importer->loadXmlFile($dir . "/catalog.xml");
 $importer->setUpdateIgnoreMode(); // режим НЕ обновления уже существующих записей
 $importer->setFilesSource($dir); // путь до файлов
 $importer->execute(); 

На что следует обратить внимание во избежание проблем с импортом:

 - Для вывода сообщений работы xmlImporter'a так же как у xmlEmporter'a существует
 свой метод - "getImportLog()".
 - Для корректной настройки импорта настоятельно рекомендуем 
 ознакомиться с возможностями класса xmlImporter.