Это первая часть из темы "3. Migrate, Feeds и свой вариант импорта и обновления данных."
Мы напишем свой собственный модуль для импорта данных из excel файла. Давайте представим что нам нужно импортировать список планшетов с некоторыми характеристиками на наш сайт.
Есть Excel файл с колонками :
--------------|----------------|------------------------|-----------------------------------|-------------
|Таксономия | Текстовое поле | Большое текстовое поле | Таксономия с множественным вводом | Изображение
--------------|----------------|------------------------|-----------------------------------|-------------
|Объединенное тестовое поле | Уже готовое поле Body |
|являющееся заголовком ноды |
Создадим тип материала Планшет, с данными полями. Тип полей указан во второй строке.
Получаем :
Файл который мы будем импортировать: Файл для импорта
Теперь приступим к написанию модуля. Модуль в процессе написания я буду выкладывать на GitHub. Чтобы можно было взять состояние модуля на любом этапе. (Пока не могу понять как сделать ссылку на скачивание кода после определённого коммита). https://github.com/semasping/Drupal_own_import_from_file
Написание модуля разобью на несколько этапов.
I. Для начала. Загрузка файла. Получения массива. Импорт в Drupal.
II. Модифицируем Импорт для работы через Batch, и добавим возможность загружать архив с изображениями
III. Модифицируем модуль так чтоб протащить различные настройки из формы в Batch обработчик.
Итак Первый этап.
Разместим вход в наш модуль на сайте через меню «Содержимое», так как работа будет происходить по импорту материалов.
1. Создадим страницу (форму) загрузки файла.
<?php
function own_import_from_file_menu() {
$items['admin/content/own_import'] = array(
'title' => 'Import from file',
'page callback' => 'drupal_get_form',
'access arguments' => array('own_import_from_file'),
'file' => '',
);
return $items;
}
function
own_import_from_file_permission() {
return array(
'own_import_from_file' => array(
'title' => t('own import from file'),
'description' => t('own import from file')
),
);
}
function
own_import_from_file_form()
{
$form = array(
'#attributes' => array('enctype' => "multipart/form-data"),
);
$form['import_file'] = array(
'#type' => 'file',
'#title' => 'Файл для импорта',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Import')
);
return $form;
}
?>
Давайте зайдем на сайт и посмотрим на нашу страницу. Перед этим естественно надо включить наш модуль. А если он уже включен то обязательно очистим кэш сайта, так как мы внесли изменения в систему меню своей функцией "own_import_from_file_menu()".
А вот и наша страница:
2. Далее нам необходимо написать функцию проверки отправленного файла. Приступим.
<?php
function own_import_from_file_form_form_validate($form, &$form_state) {
$path_parts = pathinfo($_FILES['files']['name']['import_file']);
if (!isset($path_parts["extension"])) {
form_set_error('import_file', t('File not choose'));
}
if (isset(
$path_parts["extension"]) && ($path_parts["extension"] != 'xlsx') && ($path_parts["extension"] != 'xls')) {
form_set_error('import_file', t('Wrong type of file! Choose Excel file. You choose: ext', array('ext'=>$path_parts["extension"])));
}
}
?>
В данной функции мы обрабатываем расширение файла. А так же выводим ошибку если файл не выбран. (прим. Автора: В английских словах и фразах могут быть ошибки. Но я думаю что лучше с ошибками, но через функцию перевода t(), чтобы сразу привыкать к правильному подходу.)
3. Переходим к этапу обработки файла.
Полученный файл нам необходимо сохранить. Так же в этой функции мы можем обработать различные параметры которые мы можем запросить в форме. (К дополнительным параметрам мы перейдем позже.)
<?php
function own_import_from_file_form_submit($form, &$form_state) {
$dir = 'public://import_files/';
file_prepare_directory($dir, FILE_CREATE_DIRECTORY); // проверка на наличие директории.
$file = $dir . $_FILES['files']['name']['import_file'];
if ($result = move_uploaded_file($_FILES['files']['tmp_name']['import_file'], $file)) {
own_import_from_file_work($file); // передаем наш файл на дальнейшую обработку.
}else{
form_set_error('import_file', t('Error with saving. File not saved. Try another file or contact the administrator.'));
return '';
}
}
?>
4. Обработка файла.
Мы будем импортировать excel файл, соответственно нам понадобится библиотека для работы с excel файлами. Для этого мы возьмем библиотеку PHPExcel. Чтобы проинформировать пользователя о необходимости данной библиотеки при установке модуля создадим файл «own_import_from_file.install» и добавим следующую информацию:
<?php
function own_import_from_file_install() {
drupal_set_message(t("In order to use own_import_from_file, you must first download the entire library at !link and put it under sites/all/libraries/PHPExcel/ (so you should have sites/all/libraries/PHPExcel/PHPExcel.php)", array('!link' => '<a href="http://www.phpexcel.net">phpexcel.net</a>')), 'warning');
}
?>
Теперь при включении нашего модуля мы увидим такой скрин:
Скачаем библиотеку с сайта http://www.phpexcel.net/ и положим ее в sites/all/libraries/PHPExcel/ (так чтобы получился следующий путь: sites/all/libraries/PHPExcel/PHPExcel.php).
Пишем функцию обработки нашего файла. В функции own_import_from_file_work($file); нам нужно получить массив из файла и передать его в функцию для импорта.
<?php
function own_import_from_file_work($file) {
drupal_set_message('Тут мы будем работать с файлом.');
$arr_from_file = own_import_from_file_get_array_from_file($file);
dpm($arr_from_file);
return '';
}
?>
Данная функция будет преобразовывать файл в массив.
<?php
function own_import_from_file_get_array_from_file($file) {
$lib_dir = libraries_get_path('phpexcel');
require_once(
$lib_dir . '/PHPExcel/IOFactory.php');
$objPHPExcel = PHPExcel_IOFactory::load(drupal_realpath($file));
$objPHPExcel->setActiveSheetIndex(0);
$aSheet = $objPHPExcel->getActiveSheet();
// обрабатываем каждую строку
foreach ($aSheet->getRowIterator() as $rkey => $row) {
$cellIterator = $row->getCellIterator();
// в зависмости от номера строки создаем массив заголовков.
switch ($rkey) {
case 1: // Нормально название колонки
break;
case 2: // Название колонки для Drupal
foreach ($cellIterator as $ckey => $cell) {
$kkey[$ckey] = $cell->getCalculatedValue();
}
break;
default:
// обрабатываем каждую ячейку строки
foreach ($cellIterator as $ckey => $cell) {
$res[$rkey][$kkey[$ckey]] = trim($cell->getCalculatedValue());
}
}
}
return $res;
}
?>
Давайте посмотрим на получившуюся структуру массива.
Из структуры мы видим одну ошибку связанную с форматом ячеек в Excel. При использовании «запятой» как разделителя в поле «Внутренняя память» мы получаем дробное число. Исправить это можно заменив разделитель значений «,» на «;». Хотя думаю что можно покопаться в библиотеке PHPExcel и найти решение там. Разделителем может быть любой символ, который вам удобен. Главное что на текущем этапе нам нужно чтобы данные из файла попадали в массив без изменений.
И так массив у нас есть. Теперь нам нужно узнать структуру «ноды» для импорта. Идем на api.drupal.org смотреть в каком виде принимает данные функция node_save($node);
В комментариях находим:
<?php
$node = new stdClass();
$node->type = 'article';
node_object_prepare($node);
$node->title = 'Title';
$node->language = LANGUAGE_NONE;
?>
Соответственно у нас $node->type = ‘planshet’;
Но этого нам не достаточно. Нам необходимо знать, в каком виде нужно сохранять информацию в полях нашей ноды. Давайте добавим тестовую запись «Test title». И при наличии модуля devel мы можем посмотреть на вкладке «Разработка» структуру нашей ноды.
Тут куча информации но нам нужна только часть.
Значение заголовка:
Значение большого текстового поля:
Значение простого текстового поля:
Значение поля - ссылка не термин:
Значение поля с изображение, (аналогично с любым файлом):
Теперь мы знаем как нужно сформировать массив чтобы передать информацию о таксономии, файле, простом текстовом поле, большом текстовом поле.
Приступим к написанию функции сохранения полученного массива в drupal.
<?php
function own_import_from_file_save_nodes($arr_from_file) {
foreach ($arr_from_file as $row_key => $row_value) {
$node = new stdClass();
$node->type = 'planshet';
node_object_prepare($node);
$node->title = $row_value['field_vendor_pad'] . ' ' . $row_value['field_model_pad'];
$node->language = LANGUAGE_NONE;
$node->body[$node->language] = _own_import_from_file_get_value($row_value['body'], 'bigtext');
$node->field_model_pad[$node->language] = _own_import_from_file_get_value($row_value['field_model_pad'], 'text');
$node->field_vendor_pad[$node->language] = _own_import_from_file_get_tid($row_value['field_vendor_pad'], 'vendor');
$node->field_hdd_pad[$node->language] = _own_import_from_file_get_tid($row_value['field_hdd_pad'], 'hdd');
node_save($node);
drupal_set_message(t('Imported !title with nid: nid', array(
'!title' => l($node->title, 'node/' . $node->nid),
'nid' => $node->nid
)));
}
}
?>
Эта функция формирует необходимый нам объект для передачи в node_save();
Получение значений для различных полей мы вынесли в отдельные функции.
Соответственно эти функции:
<?php
function _own_import_from_file_get_value($value, $type) {
switch ($type) {
case 'bigtext':
$format = 'filtered_html';
$ret_value[] = array('value' => check_markup($value, $format), 'format' => $format);
break;
case 'text':
$ret_value[] = array('value' => $value);
break;
default:
$ret_value[] = $value;
}
return $ret_value;
}
function
_own_import_from_file_get_tid($value, $vocab_name) {
$arr_val_tax = explode(';', $value);
foreach ($arr_val_tax as $val_tax) {
$term = taxonomy_get_term_by_name($val_tax, $vocab_name);
if (!empty($term)) {
$arr_tid[] = reset($term)->tid;
}
else {
$vocabulary = taxonomy_vocabulary_machine_name_load($vocab_name);
$term = new stdClass();
$term->name = $val_tax;
$term->vid = $vocabulary->vid;
taxonomy_term_save($term);
$arr_tid[] = $term->tid;
}
}
return $arr_tid;
}
?>
На данном этапе модуль закончен.
В нем много чего не учтено. Например:
- отсутствует проверка на дублирование ноды при импорте.
- модуль не умеет обновлять существующие ноды
- не подходит для большого количества импортируемой информации (думаю не больше 100 строк в Excel).
Но общее направление уже понятно.
Итоговый код можно взять на GitHub https://github.com/semasping/Drupal_own_import_from_file (скорее всего будут вносится некоторые правки.)
Либо в приложении: http://www.drupal.ru/files/own_import_from_file.zip
Пытался сюда приложить архив с модулем в формате tar.gz - получил ошибку:
Вложение | Размер |
---|---|
Поля типа материала | 263.71 КБ |
Старница формы загрузки файла | 47.64 КБ |
Файл для импорта | 10.71 КБ |
Информация об установке дополнительной билиотеки | 235.55 КБ |
Структура массива из файла | 293.15 КБ |
Структура ноды | 314.81 КБ |
Структура ноды - значение заголовка | 11.47 КБ |
Структура ноды - значение поля Body | 77.27 КБ |
Структура ноды - значение простого текстового поля | 50.86 КБ |
Структура ноды - значение поля таксономии | 72.11 КБ |
Структура ноды - значение поля с файлом | 161.5 КБ |
Ошибка при загрузке архива типа tar.gz | 78.6 КБ |
Готовый модуль - own_import_from_file.zip | 40.99 КБ |
Комментарии
рекомендую обязательно начать с описания отличий feeds & migrate - разные прикладные возможности.
оба модуля предлагают свои механизмы для обработки получаемых значений и работы со связанными сохраняемыми сущностями
Я тоже пытался долгое время экспортировать товар из feeds - результат куча неправильных данных: корявые термины таксономии, лишние и ненужны файлы с картинками типа image1_23.jpg (где 23 порядковый номер файла а кроме того в директории files лежат еще 22 файла с порядковыми номерами 1-22 и это все одинаковые картинки), отсутствие описания у изображение (теги alt и title) и т.д. И починить это оказалось невозможным.
В итоге: написали кастомный модуль по закачке материалов на сайт работает стабильно и правильно - но не быстро Так как используются стандартные функции Drupal node_save и node_load - а это тормоза, пока не оптимизируем ничего, объем экспорта небольшой просто. Модуль migrate не пользовал, но о нем хорошо отзываются.
Удачи вам.
С ув., Алексей
К сожалению ни с одним из этих модулей не работал.
А опыт самописного импорта есть, поэтому с него и начал. После этой статьи, возможно разберусь с модулями и опишу импорт этой же таблицы через данные модули.
Но так как это моя первая статья - то написание идет очень медленно. Спасибо.