phpBB to bbPress

 Итак, недавно я столкнулся с задачей перенести форум с phpBB на bbPress, а вручную это делать ни один умный человек не стал бы. "Ну чем я хуже?" - подумал я и тоже не стал.

Для самых нетерпеливых: если вам нужно тупо сделать перенос, и неинтересно читать и разбираться, что к чему - внизу статьи ссылка на сам скрипт, он хорошо закомментирован, разберетесь. 
Для остальных продолжаем =)

Сразу одна оговорка: статья написана с предположением того, что таблицы форума и вордпресса у вас лежат в одной и той же базе данных; если это не так, то просто доработайте скрипт так, чтобы он осуществлял 2 подключения к разным базам. Также я забил на тот факт, что префиксы у таблиц могут быть разные, у меня они phpbb_ и wp_, но и этот момент при необходимости исправления тоже не требует никаких сложных манипуляций.

А, стоп, еще одна оговорка, это не просто bbPress, а тот, что ставится вместе с BuddyPress, так что учитывайте этот момент, если что-то пойдет не так, а то возможно там где-то что-то да отличается.

Основная сложность задачи состоит в том, что много всякой чухни нужно непонятно откуда взять и непонятно куда положить. Но спокойно, детвора, я с этим разобрался! ;)

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

Поля phpbb_users, данные из которых нам пригодятся, следующие:
user_id, username_clean, username, user_email, user_website, user_regdate, username;
поле с паролем нам не пригодится кроме как в случае, если вы сможете его расшифровать, так что далее мы будем генерировать новые пароли и шифровать средствами wordpress, чтобы потом разослать всем письмо с новым паролем, или же на крайняк пусть зайдут да по форме восстановления сами уладят этот вопрос.

В таблице wp_users для нас интерес представляют следующие поля:
ID - все понятно
user_login - та ж фигня
user_pass - обсуждали уже
user_nicename - вроде никнейма, как я понял
user_email - с этим ясно
user_url - сайт юзера
user_registered - дата регистрации
user_status - подтвержденный ли пользователь
display_name - пункт "Отображать как" в админке в профиле

Помимо этих основных полей некоторые данные содержатся еще в таблице wp_usermeta в виде связи "id пользователя - ключ - значение". Т.е. еще нам понадобится внести в эту таблицу такое данные, как:
wp_capabilities - роли и права доступа, first_name, nickname...
да и по желанию много чего другого, например, дату рождения или город нахождения

Ну все, хорош эту мочалку жевать про внутреннее устройство и бла бла бла, поехали уже кодить!

// сперва небольшая функция для генерации паролей
function gen_pass($sym_num) // $sym_num - количество символов в пароле
{
     $symbols = 'aLbKcZ0dJeIfH1gh7iGYj9XkFlmEn8oMWpDq3VrN5sOCt2uBvU6PwQ4xRyTAzS';
     $sym_len = strlen($symbols) - 1;
 
     $pass = '';
 
     for ( $i = 0; $i < $sym_num; $i++ )
     {
         $pass .= $symbols{rand(1, $sym_len)};
     }
 
     return $pass;
}

// подключаем библиотеку (входит в wp), которая занимается шифрованием, путь указан относительно корня сайта, так как мой скрипт миграции лежит именно там
include('wp-includes/class-phpass.php');
$wp_hasher = new PasswordHash(8, TRUE); // создаем объект, который будет хэшировать наши пароли

// получаем всех пользователей из таблицы форума phpBB
$result = mysql_query('SELECT * FROM `phpbb_users` WHERE `user_type` = 0');
while ( $row = mysql_fetch_assoc($result) )
{
     // генерируем пароль
     $pass = gen_pass(8);
 
     // пока он еще не зашифрован, записываем в отдельный файл (из него потом можно их взять и автоматом разослать юзерам
     file_put_contents('passes.txt', $row['user_email'].' '.$pass."\n", FILE_APPEND);
 
     // и хэшируем
     $pass = $wp_hasher->HashPassword($pass);
 
     $regdate = date('Y-m-d H:m:s', $row['user_regdate']);
 
     // собственно переносим в таблицу wp
     mysql_query("INSERT INTO `wp_users` (`ID`, `user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_registered`, `user_status`, `display_name`) VALUES ('{$row['user_id']}', '{$row['username_clean']}', '{$pass}', '{$row['username']}', '{$row['user_email']}', '{$row['user_website']}', '$regdate', 0, '{$row['username']}')");
 
     // еще нужно обозначить роли всякие, подробнее на http://codex.wordpress.org/Roles_and_Capabilities
     mysql_query("INSERT INTO `wp_usermeta` (`user_id`, `meta_key`, `meta_value`) VALUES ({$row['user_id']}, 'wp_capabilities', 'a:1:{s:10:\"subscriber\";s:1:\"1\";}')");
 
     // и прочее
     mysql_query("INSERT INTO `wp_usermeta` (`user_id`, `meta_key`, `meta_value`) VALUES ({$row['user_id']}, 'first_name', '{$row['username']}')");
     mysql_query("INSERT INTO `wp_usermeta` (`user_id`, `meta_key`, `meta_value`) VALUES ({$row['user_id']}, 'nickname', '{$row['username']}')");
}

Ффух.. справились. У меня там каша варится, сгоняю проверю, а вы пока передохните.

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

Как вы помните, в случае с юзерами многие данные хранятся в отдельной таблице с метаданными, так вот то же верно и для постов (а в bbPress форумы, топики и ответы - это не что иное, как посты с post_type = forum, topic или reply). И поскольку при конвертации форума нам нужно будет очень много таких данных прописать, то следующая функция будет кстати. С ней по-моему все понятно.

function putmeta($post_id, $array)
{
    foreach ( $array as $key => $value )
     {
     mysql_query("INSERT INTO `wp_postmeta` (`post_id`, `meta_key`, `meta_value`) VALUES ('$post_id', '$key', '$value')");
     }
}

/*
Далее $f_id - id форума
 $f_name - его имя/заголовок
 $f_desc - описание/текст
 
стоит обратить внимание на поле `post_name`, по нему будет формироваться URL, у меня они все будут вида forum-{$f_id}, то бишь forum-1, forum-2, forum-10, вы можете указать свой формат.

И еще полем guid лучше не пренебрегать, и задавать его, так как тупо цитируя WP Codex:
The term "GUID" stands for "Globally Unique Identifier". It is a field that is intended to hold an identifier for the post which a) is unique across the whole of space and time and b) never, ever changes.
*/

function putforum($f_id, $f_name, $f_desc)
{
     mysql_query("INSERT INTO `wp_posts` (
 `ID`, `post_type`, `post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`, `post_status`, `comment_status`, `ping_status`, `post_name`, `post_modified`, `post_modified_gmt`, `post_parent`) 
 VALUES (
 NULL, 'forum', 1, NOW(), NOW(), '$f_desc', '$f_name', 'publish', 'open', 'open', 'forum-{$f_id}', NOW(), NOW(), 0)");
 
     $ins_id = mysql_insert_id();
 
     mysql_query("UPDATE `wp_posts` SET `guid` = 'http://ВАШСАЙТ.РУ/?post_type=forum&#038;p=$ins_id' WHERE `ID` = $ins_id"); // замените, пожалуйста, ВАШСАЙТ.РУ, если вы сами не заметили ничего странного в запросе
 
     return $ins_id; // возвращаем айди созданного форума, пригодится
}

/*
Далее $forum - форум, в котором находится создаваемый топик, т.е. грубо говоря родительский пост для создаваемого поста (post_parent)
 $t_id - id топика
 $t_time - время создания в виде Y-m-d H:i:s
 $t_poster - id топик-стартера (ну, кто начал топик)
 $t_title - понятно что
 $fp_text - first post text; в phpBB топик не хранит заголовок и текст в таблице phpbb_topics, вместо этого там имеется поле first_post_id. В bbPress наоборот, топик сам и является первым своим постом и может (и должен) содержать в себе текст
*/

function puttopic($forum, $t_id, $t_time, $t_poster, $t_title, $fp_text)
{
     mysql_query("INSERT INTO `wp_posts` (`ID`, `post_type`, `post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`, `post_status`, `comment_status`, `ping_status`, `post_name`, `post_modified`, `post_modified_gmt`, `post_parent`) 
 VALUES (NULL, 'topic', '$t_poster', '$t_time', '$t_time', '$fp_text', '$t_title', 'publish', 'open', 'open', 'topic-{$t_id}', '$t_time', '$t_time', '$forum')");
 
     $ins_id = mysql_insert_id();
 
     mysql_query("UPDATE `wp_posts` SET `guid` = 'http://ВАШСАЙТ.РУ/?post_type=topic&#038;p=$ins_id' WHERE `ID` = $ins_id");
 
     return $ins_id;
}

/*
Далее $topic - id топика, к которому относится пост
 $p_id, $p_time, $p_poster, $p_name, $p_text - то же, что и в предыдущей функции, только относительно ответов
*/

function putpost($topic, $p_id, $p_time, $p_poster, $p_name, $p_text)
{
     mysql_query("INSERT INTO `wp_posts` (
 `ID`, `post_type`, `post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`, `post_status`, `comment_status`, `ping_status`, `post_name`, `post_modified`, `post_modified_gmt`, `post_parent`) 
 VALUES (NULL, 'reply', '$p_poster', '$p_time', '$p_time', '$p_text', '$p_name', 'publish', 'open', 'open', 'reply-{$p_id}', '$p_time', '$p_time', '$topic')");
 
     $ins_id = mysql_insert_id();
 
     mysql_query("UPDATE `wp_posts` SET `guid` = 'http://ВАШСАЙТ.РУ/?post_type=reply&#038;p=$ins_id' WHERE `ID` = $ins_id");
 
     return $ins_id;
}

В моем случае на форуме phpBB была только одна категория, и в ней все форумы. У категорий там forum_type = 0, у форумов 1. То есть у нужной мне категории форумов были forum_type = 0, forum_id = 1, в итоге для получения списка форумов мы будем делать выборку по критерию `forum_type` = 1 AND `parent_id` = 1.

И вообще алгоритм будет следующий:

получаем форумы
пока ( форумы_получаются )
{
     добавляем форум в ВП

     получаем топики текущего форума
     пока ( топики_получаются )
     {
         добавляем топик в ВП

         получаем посты текущего топика
         пока ( посты получаются )
         {
             добавляем пост в ВП
             запихиваем мета-данные касательно текущего поста
         }

         запихиваем мета-данные касательно текущего топика
     }

     запихиваем мета-данные касательно текущего форума
}

На пальцах объяснил, теперь попробуйте разобраться непосредственно в коде.

$result = mysql_query('SELECT `forum_id`, `parent_id`, `forum_name`, `forum_desc`, `forum_posts`, `forum_topics` 
 FROM `phpbb_forums` WHERE `forum_type` = 1 AND `parent_id` = 1 ORDER BY `forum_id` ASC');

while ( $row = mysql_fetch_array($result, MYSQL_NUM) )
{
    // id форума, id родительского форума, имя форума, описание, кол-во постов, кол-во топиков
    list($f_id, $par_id, $f_name, $f_desc, $f_posts, $f_topics) = $row;
 
    // добавляем форум
    $new_f_id = putforum($f_id, $f_name, $f_desc);

    // получаем топики текущего ($f_id) форума
    $topic_res = mysql_query("SELECT `phpbb_topics`.`topic_id` , `phpbb_topics`.`topic_title` , `phpbb_topics`.`topic_poster` , `phpbb_topics`.`topic_time` , `phpbb_posts`.`post_text`, `phpbb_topics`.`topic_last_post_time`, `phpbb_topics`.`topic_replies`, `phpbb_topics`.`topic_first_post_id` 
FROM `phpbb_topics` 
LEFT JOIN `phpbb_posts` ON `phpbb_posts`.`post_id` = `phpbb_topics`.`topic_first_post_id` 
WHERE `phpbb_topics`.`forum_id` = '$f_id' 
ORDER BY `phpbb_topics`.`topic_id` ASC");
 
    // пока топики получаются
    while ( $t_row = mysql_fetch_array($topic_res, MYSQL_NUM) )
    {
        // id топика, заголовок, id топик-стартера, время публикации, текст первого поста, время публикации последнего поста (в мета-данных понадобится), кол-во ответов, id первого поста
        list($t_id, $t_title, $t_poster, $t_time, $fp_text, $lp_time, $t_replies, $fp_id) = $t_row;
 
        // на phpBB использовались настройки часового пояса, у меня такого не было, так что пришлось подкручивать вручную здесь: -3600 секунд
        $t_time = date('Y-m-d H:i:s', $t_time - 3600);
        $lp_time = date('Y-m-d H:i:s', $lp_time - 3600);
 
        // вставляем топик
        $new_t_id = puttopic($new_f_id, $t_id, $t_time, $t_poster, $t_title, $fp_text);
 
        // $new_t_id с каждой итерацией обновляется, но по окончании цикла переменная $last_topic_id будет содержать последний из вставленных топиков
        $last_topic_id = $new_t_id;
 
        // получаем посты текущего топика (по $f_id, $t_id и, чтобы не повторять текст первого поста, исключаем $fp_id, т.е. first post id)
        $posts_res = mysql_query("SELECT `post_id`, `poster_id`, `post_time`, `post_subject`, `post_text` 
FROM `phpbb_posts` WHERE `forum_id` = $f_id AND `topic_id` = $t_id AND `post_id` <> $fp_id ORDER BY `post_time` ASC");
 
        // еще есть такое понятие, как voices, или голоса - количество участников топика, обычно отличается от количества постов
        $voices_res = mysql_query("SELECT `post_id` FROM `phpbb_posts` WHERE `forum_id` = $f_id AND `topic_id` = $t_id GROUP BY `poster_id`");
        $voices = mysql_num_rows($voices_res);
        unset($voices_res);
 
        // пока посты получаются
        while ( $p_row = mysql_fetch_array($posts_res, MYSQL_NUM) )
        {
            // id поста, id автора, время поста, заголовок, текст
            list($p_id, $p_poster, $p_time, $p_name, $p_text) = $p_row;
 
            $p_time = date('Y-m-d H:i:s', $p_time - 3600);
 
            // вставляем пост
            $new_p_id = putpost($new_t_id, $p_id, $p_time, $p_poster, $p_name, $p_text);
 
            // формируем мета-данные, которые хотим внести
            $meta = array(
                                '_bbp_topic_id' => $new_t_id,
                                '_bbp_forum_id' => $new_f_id
                                );
             // и вносим в недавно вставленный пост
            putmeta($new_p_id, $meta);
 
            $last_active_id = $new_p_id;
        }
 
        $meta = array (
                             '_bbp_forum_id' => $new_f_id,
                             '_bbp_topic_id' => $new_t_id,
                             '_bbp_last_active_id' => $last_active_id,
                             '_bbp_last_active_time' => $lp_time,
                             '_bbp_reply_count' => $t_replies,
                             '_bbp_voice_count' => $voices,
                             '_bbp_last_reply_id' => $last_active_id
                             );
 
         putmeta($new_t_id, $meta);
    }
 
    $meta = array(
                        '_bbp_topic_count' => $f_topics,
                        '_bbp_total_topic_count' => $f_topics,
                        '_bbp_reply_count' => $f_posts,
                        '_bbp_total_reply_count' => $f_posts,
                        '_bbp_last_active_time' => $lp_time,
                        '_bbp_last_active_id' => $last_active_id,
                        '_bbp_last_reply_id' => $last_active_id,
                        '_bbp_last_topic_id' => $last_topic_id
                        );
 
    putmeta($new_f_id, $meta);
}

Ох, потрудились мы на славу! Пойду заточу шоколадку.

Если есть вопросы, пишите в комментариях. Спасибо за внимание! =)

Обещанная ссылка на скрипт вот http://rusfolder.com/31724334 (convert phpBB to bbPress, BuddyPress)

 Жду с нетерпением
ваших комментариев!
 

Подписаться на RSS

Вы можете нажать "подписаться", чтобы следить за моими новостями!
Так вы всегда будете в курсе появления новостей на сайте =)
О том, что такое RSS можно прочитать здесь.

Подписаться

Подписаться на Twitter

Я специально зарегистрировался в Твиттере, чтобы вы могли следить за обновлениями на сайте =)

Подписаться

Envato marketplace А эти люди занимаются прокатом карнавальных костюмов и масок в Минске. К слову, я им делал сайт.