Материал просмотрен 328 раз(а)

Возникла как-то задача преобразования одного сайта (построенного на CMS 1C Bitrix) в WordPress – популярную блоговую платформу. Пришлось самостоятельно разбираться в форматах данных и писать конвертор базы одного сайта в базу другого. Может кому-то покажутся полезными мои наработки.

Писал код в разное время, он жутко неоптимален и местами кривой, но работает. (написано на PHP).

Почему я решил переходить на WordPress? Эта платформа мне показалась более дружелюбна, чем Bitrix. Кроме того, под платформу WordPress есть множество обучающих материалов (подробнее), чего я не смог найти под Bitrix.

Итак, некоторые пояснения о том, как хранят данные Bitrix и WordPress. Для полного понимания что и как советую использовать phpmyadmin:

pma

pma

Посты

Посты, т.е. по простому говоря статьи хранятся в следующем виде в таблице b_iblock_element (далее – поля):

  • ID – идентификатор поста;
  • TIMESTAMP_X, MODIFIED_BY, DATE_CREATE_CREATED_BY – временные метки;
  • IBLOCK_ID – идентификатор iblock-а, множества всех объектов в системе Bitrix;
  • IBLOCK_SECTION_ID – идентификатор категории
  • NAME – заголовок поста
  • DETAIL_TEXT – HTML-содержимое поста

Картинки в медиабиблиотеке

С картинками поступим немного иначе. Анализируем поле DETAIL_TEXT каждой статьи, находим там картинки, использованные в этой статье. Извлекаем их сразу в папку. Конечно можно искать по базе, но я решил все сделать иначе. Уже и не вспомню почему.

Итак, принцип действия такой. Сперва я формирую служебные XML-файлы, в которые экспортирую всю базу Битрикса (то, что буду использовать в импорте в WordPress), потом другим скриптом эти XML буду разбирать и заполнять базу вордпресса.

Категории, к которым относится пост

Категории хранятся в таблице b_iblock_section_element, где есть поле iblock_element_id, которое не что иное как идентификатор поста (ID), и одна-несколько записей в поле iblock_section_id – идентификатор категории. Будем сохранять численные значения.

Пока этих данных нам хватит:

$POST_FILE = fopen('posts.xml','w');
// определяем файл для экспорта постов
$_OLD['BASE'] = 'basename_bitrix';
$_OLD['USER'] = '*****';
$_OLD['PASS'] = '*****';
$IMG_FILE = fopen('images.xml','w');
// определяем файл для записи картинок. Позднее я буду использовать эти переменные в других целях.
$_OLD['TABLE_POST'] = 'b_iblock_element';
// имя служебной таблицы
$_OLD['TABLE_POST_DATE'] = 'ACTIVE_FROM';
// в качестве даты поста буду использовать это поле
$_OLD['TABLE_POST_CAPTION'] = 'NAME';
// а это будет заголовок
$_OLD['TABLE_POST_TEXT'] = 'DETAIL_TEXT';
// в этом поле хранится содержимое поста
$_OLD['TABLE_POST_COUNT_SHOW'] = 'SHOW_COUNTER';
// а тут - количество просмотров каждой статьи
$_OLD['MEDIAPREFIX'] ='';
$conOLD = mysql_connect('localhost',$_OLD['USER'],$_OLD['PASS'],1) or die(mysql_error());
$con2 = mysql_connect('localhost',$_OLD['USER'],$_OLD['PASS'],1) or die(mysql_error());
mysql_select_db($_OLD['BASE'],$conOLD);
mysql_query('set names utf8');
mysql_select_db($_OLD['BASE'],$con2);
fwrite($POST_FILE,'<?xml version="1.0" encoding="UTF-8" ?>'.PHP_EOL);
fwrite($IMG_FILE,'<?xml version="1.0" encoding="UTF-8" ?>'.PHP_EOL);
//пихаем шапку XML-а
$rez = mysql_query('SELECT * FROM `'.$_OLD['TABLE_POST'].'` ORDER BY `'.$_OLD['TABLE_POST_DATE'].'` DESC');
// берем все посты, сортируем по дате
fwrite($POST_FILE,'<posts>'.PHP_EOL);
fwrite($IMG_FILE,'<images>');
$count_post = 0;
while ($row = mysql_fetch_assoc($rez)) {
$count_post++;
fwrite($POST_FILE,'<post>'.PHP_EOL);
fwrite($POST_FILE,'<count>'.$count_post.'</count>'.PHP_EOL);
fwrite($POST_FILE,'<id>'.$row['ID'].'</id>'.PHP_EOL);
fwrite($POST_FILE,'<date_create>'.$row[$_OLD['TABLE_POST_DATE']].'</date_create>'.PHP_EOL);
fwrite($POST_FILE,'<title>'.$row[$_OLD['TABLE_POST_CAPTION']].'</title>'.PHP_EOL);
fwrite($POST_FILE,'<text>'.htmlspecialchars($row[$_OLD['TABLE_POST_TEXT']]).'</text>'.PHP_EOL);
// для каждого поста заполняем полями каждый объект XML-файла
$data = $row[$_OLD['TABLE_POST_TEXT']];
preg_match_all('/<img[^>]+>/i',$data,$arr); // вытаскиваем все картинки
if (sizeof($arr) != 0)
{
fwrite($IMG_FILE,'<image>');
fwrite($IMG_FILE,'<id_article>'.$row['ID'].'</id_article>'.PHP_EOL);
//далее пилим картинку, вытаскиваем src, title, width, height-атрибуты, создаем соответствующий XML-файл
foreach ($arr[0] as $img)
{
if (preg_match('/src="\S*"/',$img,$match) > 0) {
$match[0] = substr($match[0],5,strlen($match[0])-6);
fwrite($IMG_FILE,'<src>'.$match[0].'</src>'.PHP_EOL);
}
if (preg_match('/title="\S*"/',$img,$match) > 0) {
$match[0] = substr($match[0],7,strlen($match[0])-8);
fwrite($IMG_FILE,'<title>'.$match[0].'</title>'.PHP_EOL);
}
if (preg_match('/width="\d*"/',$img,$match) > 0) {
$match[0] = substr($match[0],7,strlen($match[0])-8);
fwrite($IMG_FILE,'<width>'.$match[0].'</width>'.PHP_EOL);
}
if (preg_match('/height="\d*"/',$img,$match) > 0) {
$match[0] = substr($match[0],8,strlen($match[0])-9);
fwrite($IMG_FILE,'<height>'.$match[0].'</height>'.PHP_EOL);
}
}
fwrite($IMG_FILE,'</image>'.PHP_EOL);
}
// получаем категории, к которым относится каждый пост. Категории хранятся в b_iblock_section_element, в поле iblock_section_id.
fwrite($POST_FILE,'<search_count>'.$row[$_OLD['TABLE_POST_COUNT_SHOW']].'</search_count>'.PHP_EOL);
$old_id = $row['ID'];
fwrite($POST_FILE,'<cats>'.PHP_EOL);
$query = 'SELECT * FROM `b_iblock_section_element` WHERE `IBLOCK_ELEMENT_ID` = ' . $old_id;
$rez2 = mysql_query($query);
while ($row2 = mysql_fetch_assoc($rez2))
{
fwrite($POST_FILE,'<cat_ID>'.$row2['IBLOCK_SECTION_ID'].'</cat_ID>'.PHP_EOL);
}
fwrite($POST_FILE,'</cats>'.PHP_EOL);
fwrite($POST_FILE,'</post>'.PHP_EOL);
}
fwrite($POST_FILE,'</posts>');
fwrite($IMG_FILE,'</images>');
fclose($POST_FILE);

А тут мы формируем файл категорий, т.е. разделы сайта.

$POST_FILE = fopen('cats.xml','w');
$count_cats = 0;
$query = 'SELECT * FROM `b_iblock_section`';
$rez = mysql_query($query);
fwrite($POST_FILE,'<?xml version="1.0" encoding="UTF-8" ?>'.PHP_EOL.'<cats>'.PHP_EOL);
while ($row = mysql_fetch_assoc($rez)) {
$count_cats++;
fwrite($POST_FILE,'<cat>'.PHP_EOL);
fwrite($POST_FILE,'<cat_id>'.$row['ID'].'</cat_id>'.PHP_EOL);
fwrite($POST_FILE,'<cat_name>'.$row['NAME'].'</cat_name>'.PHP_EOL);
$temp = ($row['IBLOCK_SECTION_ID'] == '5') ? '0':$row['IBLOCK_SECTION_ID'];
if ($temp == '') { $temp = 0;}
fwrite($POST_FILE,'<cat_parent>'.$temp.'</cat_parent>'.PHP_EOL);
fwrite($POST_FILE,'</cat>'.PHP_EOL);
}
fwrite($POST_FILE,'</cats>');
fclose($POST_FILE);
fclose($IMG_FILE);

Вот такой код получился. Не слишком блестящий, но действует! На выходе имеем следующие файлы:

 

Категории

Категории

Тут понятно. cat_id – ID категории, cat_name – название раздела, cat_parent – родительская категория (0 – корневая).

images

Картинки

Тоже несложно, id статьи, src картинки, ширина и высота.

Посты

Посты

Тоже всё просто. Заголовок, дата, id и текст статьи.

Так, имеем такие вот XML-файлы. Теперь надо сформировать из них базу вордпресса.

Кстати, следующий скрипт весьма длителен по времени, поэтому советую сделать set_time_limit(0) чтобы убрать ограничение в 30 секунд на выполнение скрипта.

<?php
set_time_limit(0);
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
include('simple.image.php');
// Импортируем категории
$strarr = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; // уникальные идентификаторы
$con = mysql_connect('localhost','*****','************',1);
mysql_select_db('**********',$con);
mysql_query('set names utf8',$con);
$ar = simplexml_load_file('cats.xml');
for ($i = 0; $i < sizeof($ar); $i++)
{
$query = 'INSERT INTO `jmv_terms` (`term_id`,`name`,`slug`,`term_group`) VALUES (\''.($ar->cat[$i]->cat_id).'\',\''.$ar->cat[$i]->cat_name.'\',\''.$strarr[$i].'\',\'0\')';
mysql_query($query) or die(mysql_error());
$query = 'INSERT INTO `jmv_term_taxonomy` (`term_taxonomy_id`,`term_id`,`taxonomy`,`description`,`parent`,`count`) VALUES
(\''.($ar->cat[$i]->cat_id).'\',\''.($ar->cat[$i]->cat_id).'\',\'category\',\'\',\''.$ar->cat[$i]->cat_parent.'\',\'0\')';
mysql_query($query) or die(mysql_error());
}
mysql_close($con);
// Импорт категорий удался!!!!
// Импортируем медиаизображения
$ar = simplexml_load_file('images.xml');
$po = simplexml_load_file('posts.xml');
$con = mysql_connect('localhost','**********','**********',1);
mysql_select_db('**********',$con);
mysql_query('set names utf8',$con);
for ($i=0;$i<sizeof($ar);$i++)
{
//1 Получаем ID поста+
$_S_POST_ID = $ar->image[$i]->id_article;
//2 Получаем дату поста
for ($j = 0; $j<sizeof($po);$j++)
{
if ((int)$po->post[$j]->id == (int)$_S_POST_ID)
{
$_S_POST_DATE = $po->post[$j]->date_create;
}
}
//2.1 Получаем год поста
$_S_POST_YEAR = substr($_S_POST_DATE,0,4);
//2.2 Получаем месяц поста
$_S_POST_MONT = substr($_S_POST_DATE,5,2);
//3 Создаем каталог для изображения
$path = getcwd().'\wp-content\\uploads\\'.$_S_POST_YEAR.'\\'.$_S_POST_MONT.'\\';
//3.1 Проверяем, есть ли путь. Если нет - создаем
if (@mkdir($path,0755,1)) { }
//4 Копируем изображение по этому пути
foreach ($ar->image[$i]->src as $src)
{
// Начинаем махинации с путями и именами файлов. Тут наговнокодил, лень разбираться стало, писал ночью.
$img_old_path = $src;
$img_path = str_replace('/','\\',$img_old_path);
$file_in = getcwd().$img_path;
$file_out = $path.basename($img_path);
//6 Получаем имя файла
$_S_FILENAME = basename($img_path);
//5 Получаем тип файла (расширение)
$_S_TYPE = pathinfo($_S_FILENAME)['extension'];
$_S_CAPTION = '';
copy($file_in,$file_out);
$image = new SimpleImage();
$image->load($file_out);
$image->resizeToWidth(640);
$_S_PATH_640 = $path.'\\'.basename($file_out,'.'.$_S_TYPE).'-640.'.$_S_TYPE;
$_S_640_W = 640;
$image->save($_S_PATH_640);
$_S_640_H = getimagesize($_S_PATH_640)[1]; // Высота 640
$image->resizeToWidth(300);
$_S_PATH_300 = $path.'\\'.basename($file_out,'.'.$_S_TYPE).'-300.'.$_S_TYPE;
$image->save($_S_PATH_300);
$_S_300_H = getimagesize($_S_PATH_300)[1];
$_S_300_W = 300;
$image->resize(150, 150);
$_S_PATH_150 = $path.'\\'.basename($file_out,'.'.$_S_TYPE).'-150x150.'.$_S_TYPE;
$image->save($_S_PATH_150);
$_S_150_H = 150;
$_S_150_W = 150;
$f_orig = $_S_POST_YEAR.'/'.$_S_POST_MONT.'/'.$_S_FILENAME;
$f_640 = basename($file_out,'.'.$_S_TYPE).'-640.'.$_S_TYPE;
$oldim640 = 'wp-content\\uploads\\'.$_S_POST_YEAR.'\\'.$_S_POST_MONT.'\\'.$f_640;
$newim640 = 'wp-content\\uploads\\'.$_S_POST_YEAR.'\\'.$_S_POST_MONT.'\\'.basename($file_out,'.'.$_S_TYPE).'-640x'.$_S_640_H.'.'.$_S_TYPE;
rename($oldim640, $newim640);
$f_640 = basename($newim640);
$f_300 = basename($file_out,'.'.$_S_TYPE).'-300.'.$_S_TYPE;
$oldim300 = 'wp-content\\uploads\\'.$_S_POST_YEAR.'\\'.$_S_POST_MONT.'\\'.$f_300;
$newim300 = 'wp-content\\uploads\\'.$_S_POST_YEAR.'\\'.$_S_POST_MONT.'\\'.basename($file_out,'.'.$_S_TYPE).'-300x'.$_S_300_H.'.'.$_S_TYPE;
rename($oldim300, $newim300);
$f_300 = basename($newim300);
$f_150 = basename($file_out,'.'.$_S_TYPE).'-150x150.'.$_S_TYPE;
switch (getimagesize($newim300)[2])
{
case 2: { $mime = 'image/jpeg'; $mime_size = 10; break;}
case 1: { $mime = 'image/gif'; $mime_size = 9; break;}
case 3: { $mime = 'image/png'; $mime_size = 9; break;}
default: $mime = 'image/jpeg';
}
unset($image);
//А вот тут начинается колдовство! Вордпресс хранит жуткую конструкцию в виде JSON, где указаны размеры превьюшек и картинок разного размера. Будем заготавливать картинки под эти размеры. Учитываются так же и размерности названий (посимвольно)
$meta_key1 = '_wp_attached_file';
$meta_value1 = $f_orig;
$meta_key2 = '_wp_attachment_metadata';
$meta_value2 = 'a:5:{s:5:"width";i:640;s:6:"height";i:'.$_S_640_H.';s:4:"file";s:'.strlen($f_orig).':"'.$f_orig.'";s:5:"sizes";a:3:{s:9:"thumbnail";a:4:{s:4:"file";s:'.strlen($f_150).':"'.$f_150.'";s:5:"width";i:150;s:6:"height";i:150;s:9:"mime-type";s:'.$mime_size.':"'.$mime.'";}s:6:"medium";a:4:{s:4:"file";s:'.strlen($f_300).':"'.$f_300.'";s:5:"width";i:300;s:6:"height";i:'.$_S_300_H.';s:9:"mime-type";s:'.$mime_size.':"'.$mime.'";}s:14:"post-thumbnail";a:4:{s:4:"file";s:'.strlen($f_640).':"'.$f_640.'";s:5:"width";i:640;s:6:"height";i:'.$_S_640_H.';s:9:"mime-type";s:'.$mime_size.':"'.$mime.'";}}s:10:"image_meta";a:11:{s:8:"aperture";i:0;s:6:"credit";s:0:"";s:6:"camera";s:0:"";s:7:"caption";s:0:"";s:17:"created_timestamp";i:0;s:9:"copyright";s:0:"";s:12:"focal_length";i:0;s:3:"iso";i:0;s:13:"shutter_speed";i:0;s:5:"title";s:0:"";s:11:"orientation";i:0;}}';
$meta_key3 = '_wp_attachment_image_alt';
$meta_value3 = 'Foto';
$meta_key4 = '_wp_attachment_backup_sizes';
$meta_value4 = 'a:1:{s:9:"full-orig";a:3:{s:5:"width";i:640;s:6:"height";i:'.$_S_640_H.';s:4:"file";s:'.strlen($f_640).':"'.$f_640.'";}}';
// Добавляем запись в таблицу posts
// Ищем в базе $f_origin
$query2 = 'SELECT * FROM `jmv_posts` WHERE `guid` = \'http://SITE_NAME/wp-content/uploads/'.$f_orig.'\'';
if (mysql_num_rows(mysql_query($query2))!=0){continue;}
$query = 'INSERT INTO `jmv_posts` (`post_date`, `post_date_gmt`,`post_content`, `post_title`, `post_excerpt`, `post_status`, `comment_status`, `ping_status`, `post_password`, `post_name`, `to_ping`, `pinged`, `post_modified`, `post_modified_gmt`, `post_content_filtered`, `post_parent`, `guid`, `menu_order`, `post_type`, `post_mime_type`, `comment_count`) VALUES
(\''.$_S_POST_DATE.'\', \''.$_S_POST_DATE.'\', \'\', \'Foto\', \'\', \'inherit\', \'open\', \'open\', \'\', \'Foto\', \'\', \'\', \''.$_S_POST_DATE.'\', \''.$_S_POST_DATE.'\', \'\', 10, \'http://SITE_NAME/wp-content/uploads/'.$f_orig.'\', 0, \'attachment\', \''.$mime.'\', 0)';
mysql_query($query);
$LAST_ID = mysql_insert_id($con);
mysql_query('INSERT INTO `jmv_postmeta` (`post_id`,`meta_key`,`meta_value`) VALUES (\''.$LAST_ID.'\',\'_wp_attached_file\',\''.$meta_value1.'\')');
mysql_query('INSERT INTO `jmv_postmeta` (`post_id`,`meta_key`,`meta_value`) VALUES (\''.$LAST_ID.'\',\'_wp_attachment_metadata\',\''.$meta_value2.'\')');
mysql_query('INSERT INTO `jmv_postmeta` (`post_id`,`meta_key`,`meta_value`) VALUES (\''.$LAST_ID.'\',\'_wp_attachment_image_alt\',\''.$meta_value3.'\')');
mysql_query('INSERT INTO `jmv_postmeta` (`post_id`,`meta_key`,`meta_value`) VALUES (\''.$LAST_ID.'\',\'_wp_attachment_backup_sizes\',\''.$meta_value4.'\')');
}
}
mysql_close($con);
// Импортируем посты
// С каждым постом увеличиваем число в соответствующей категории
$con = mysql_connect('localhost','********','********',1);
mysql_select_db('************',$con);
mysql_query('set names utf8',$con);
$ar = simplexml_load_file('posts.xml');
foreach ($ar->post as $post)
{
$query = 'INSERT INTO `jmv_posts` (`post_date`, `post_date_gmt`,`post_content`, `post_title`, `post_excerpt`, `post_status`, `comment_status`, `ping_status`, `post_password`, `post_name`, `to_ping`, `pinged`, `post_modified`, `post_modified_gmt`, `post_content_filtered`, `post_parent`, `guid`, `menu_order`, `post_type`, `post_mime_type`, `comment_count`) VALUES
(\''.$post->date_create.'\', \''.$post->date_create.'\', \''.$post->text.'\', \''.$post->title.'\', \'\', \'publish\', \'open\', \'open\', \'\', \''.$post->title.'\', \'\', \'\', \''.$post->date_create.'\', \''.$post->date_create.'\', \'\', 0, \'???\', 0, \'post\', \'\', 0)';
mysql_query($query);
// Получаем категории
foreach ($post->cats->cat_ID as $cat_id)
{
$query2 = 'SELECT `count` FROM `jmv_term_taxonomy` WHERE (`term_taxonomy_id` = \''.$cat_id.'\')';
$row = mysql_fetch_assoc(mysql_query($query2));
$counter = (int)$row['count'];
$query3 = 'UPDATE `jmv_term_taxonomy` SET `count` = \''.($counter+1).'\' WHERE `term_taxonomy_id` = \''.$cat_id.'\'';
mysql_query($query3);
$query5 = 'INSERT INTO `jmv_term_relationships` (`object_id`,`term_taxonomy_id`) VALUES (\''.$post->id.'\',\''.$cat_id.'\')';
mysql_query($query5);
}
}
mysql_close($con);
?>

Как-нибудь наберусь сил и поправлю код на более универсальный.

P.S. Класс simple.image.php честно украден с просторов Интернета. Ресайз картинок делает.

<?php
class SimpleImage {
var $image;
var $image_type;
function load($filename) {
$image_info = getimagesize($filename);
$this->image_type = $image_info[2];
if( $this->image_type == IMAGETYPE_JPEG ) {
$this->image = imagecreatefromjpeg($filename);
} elseif( $this->image_type == IMAGETYPE_GIF ) {
$this->image = imagecreatefromgif($filename);
} elseif( $this->image_type == IMAGETYPE_PNG ) {
$this->image = imagecreatefrompng($filename);
}
}
function save($filename, $image_type=IMAGETYPE_JPEG, $compression=75, $permissions=null) {
if( $image_type == IMAGETYPE_JPEG ) {
imagejpeg($this->image,$filename,$compression);
} elseif( $image_type == IMAGETYPE_GIF ) {
imagegif($this->image,$filename);
} elseif( $image_type == IMAGETYPE_PNG ) {
imagepng($this->image,$filename);
}
if( $permissions != null) {
chmod($filename,$permissions);
}
}
function output($image_type=IMAGETYPE_JPEG) {
if( $image_type == IMAGETYPE_JPEG ) {
imagejpeg($this->image);
} elseif( $image_type == IMAGETYPE_GIF ) {
imagegif($this->image);
} elseif( $image_type == IMAGETYPE_PNG ) {
imagepng($this->image);
}
}
function getWidth() {
return imagesx($this->image);
}
function getHeight() {
return imagesy($this->image);
}
function resizeToHeight($height) {
$ratio = $height / $this->getHeight();
$width = $this->getWidth() * $ratio;
$this->resize($width,$height);
}
function resizeToWidth($width) {
$ratio = $width / $this->getWidth();
$height = $this->getheight() * $ratio;
$this->resize($width,$height);
}
function scale($scale) {
$width = $this->getWidth() * $scale/100;
$height = $this->getheight() * $scale/100;
$this->resize($width,$height);
}
function resize($width,$height) {
$new_image = imagecreatetruecolor($width, $height);
imagecopyresampled($new_image, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
$this->image = $new_image;
}
}
?>