Интеграция phpBB 3 и Kohana 3

    image
    Краткий мануал о том, как сочленить эти два инструмента. На самом деле, все не так сложно.

    Суть проблемы


    phpBB имеет свой API, вопрос как его использовать. Вообще, достаточно подключить файл common.php, однако просто так это не работает. Во-первых пересекаются классы сессий. А во-вторых и в-третьих — читайте внутри.

    Начнем


    Пусть index.php Коханы лежит в /web, а форум в /web/forum

    Инклуды

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

    forum/common.php => forum/common_kohana.php
    forum/includes/session.php => forum/includes/session_kohana.php

    Подключать впоследствии будем именно forum/common_kohana.php

    Правим session_kohana.php

    В это файле находятся два класса: Session и User. Session конфликтует с Кохановским, User я тоже переименовал. Итак, правим:

    class session // => class session_kohana

    class user extends session // => class user_kohana extends session_kohana


    Правим common_kohana.php

    Вот тут у нас идет подключения файла сессий:
    require($phpbb_root_path . 'includes/session.' . $phpEx);

    Это надо срочно исправлять:
    require($phpbb_root_path . 'includes/session_kohana.' . $phpEx);


    Опускаемся немного ниже. Здесь идет установка обработчика ошибок:
    // Set PHP error handler to ours
    set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler');
    


    У коханы уже есть свой обработчик, весьма не плохой (с блэкджеком и трейсами), так что убиваем:
    // Set PHP error handler to ours
    //set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler');
    


    Ниже создаются основные объекты, в том чилсе инстанс класса User:
    // Instantiate some basic classes
    $user		= new user();


    Этот класс хранится в файле forum/includes/session_kohana.php. Я переименовал и его тоже.
    $user		= new user_kohana();


    Теперь вот какое дело. Кохана по умолчанию убивает нафиг глобальные переменные, поэтому их нужно вручную туда добавить. Добавляем буквально ниже:
    $GLOBALS['user'] = $user;
    $GLOBALS['auth'] = $auth;
    $GLOBALS['template'] = $template;
    $GLOBALS['cache'] = $cache;
    $GLOBALS['db'] = $db;


    Дальше подключается конфиг:
    // Grab global variables, re-cache if necessary
    $config = $cache->obtain_config();


    Инстанс тоже заглобалим. Добавляем ниже
    $GLOBALS['config'] = $config;


    В самом конце файла идет установка хуков. Я до конца не разобрался насколько это влияет на работу, но вроде как они не нужны, т.к. не имеют смысла вне контекста работы юзера с форумом. Я закомментировал следующий код:
    // Add own hook handler
    require($phpbb_root_path . 'includes/hooks/index.' . $phpEx);
    $phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display')));
    
    foreach ($cache->obtain_hooks() as $hook)
    {
    	@include($phpbb_root_path . 'includes/hooks/' . $hook . '.' . $phpEx);
    }


    С common.php разобрались

    Подключение


    Я написал маленький класс, который и не класс-то в полном понимании, а набор статических фукций. Эта функция подключает либы форума, собственно код взят из файла index.php:
    public static function include_libs()
    {
    	// Два раза повторять не надо
    	if (self::$libs_included)
    		return TRUE;
    			
    	define('IN_PHPBB', true);
    	define('PHPBB_DB_NEW_LINK', 1);
    	$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './forum/';
    	$phpEx = substr(strrchr(__FILE__, '.'), 1);
    	$GLOBALS['phpbb_root_path'] = $phpbb_root_path;
    	$GLOBALS['phpEx'] = $phpEx;
    	require_once($phpbb_root_path . 'common_kohana.' . $phpEx);
    	require_once($phpbb_root_path . 'includes/functions_user.' . $phpEx);
    	require_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx);
    	
    	// Start session management
    	$user->session_begin();
    	
    	self::$libs_included = TRUE;
    	return TRUE;
    }


    Затык был в этом:
    define('PHPBB_DB_NEW_LINK', 1);

    Когда phpbb подключается к базе, он делает так:
    $db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, defined('PHPBB_DB_NEW_LINK') ? PHPBB_DB_NEW_LINK : false);


    imageЕсли не определить константу PHPBB_DB_NEW_LINK в 1, то произойдет следующее. Ваш сайт очевидно, использует базу данных. Так вот.

    Если ваш форум висит на другой базе и ваши базы все находятся на одном сервере, одном юзере (и одном и том же пароле надо полагать), то они, а точнее указатели на них (переменные типа Resource) не будут работать как положено. Функция mysql_connect, которая вызывается из phpBB, будет пытаться использовать ваш текущий кохановский mysql_connection_id, если параметры ее вызозва совпадают с теми, при которых он и был создан в Кохане. Кохановские способы котроля уникальности базы через хеши тут не работают так как лежат на уровень выше, абстрагируясь классами.
    Вот что сказано по этому поводу в документации:

    Если второй вызов функции произошёл с теми же аргументами mysql_connect(), новое соединение не будет установлено. Вместо этого функция вернёт ссылку на уже установленное соединение. Параметр new_link может заставить функцию mysql_connect() открыть ещё одно соединение, даже если соединение с аналогичными параметрами уже открыто.


    Ну вот. Теперь можно работать. Регистрация юзера происходит так:
    public static function register_user($username, $password, $email)
    {
    	global $user;
    	self::include_libs();
    	
    	$user_row = array(
    		'username'                => $username,
    		'user_password'           => phpbb_hash($password),
    		'user_email'              => $email,
    		'group_id'                => 2,
    		'user_timezone'           => 10.00,
    		'user_dst'                => 1,
    		'user_lang'               => 'ru',
    		'user_type'               => 0,
    		'user_actkey'             => '',
    		'user_ip'                 => Request::$client_ip,
    		'user_regdate'            => time(),
    		'user_inactive_reason'    => 0,
    		'user_inactive_time'      => 0,
    	);
    		
    	try
    	{
    		$user_id = user_add($user_row, FALSE);
    		return $user_id ? $user_id : FALSE;
    	}
    	catch (Exception $e)
    	{
    		return FALSE;
    	}
    }


    А так можно залогинить и разлогинить заданного юзера:
    public static function login($user_id, $persist_login = FALSE)
    {
    	global $user;
    		
    	self::include_libs();
    	$user->session_create($user_id, false, $persist_login, true);
    }
    	
    public static function logout()
    {
    	global $user;
    	
    	self::include_libs();
    	$user->session_kill(FALSE);
    	return TRUE;
    }


    Узнать залогинен ли юзер:
    public function logged_in()
    {
    	global $user;
    	$this->include_libs();
    
    	return ($user->data['user_id'] == ANONYMOUS)
    		? FALSE
    		: TRUE;
    }


    В общем можно использовать многие функции API форума, например назначать роли. Вот так происходит открытие юзеру доступа к закрытому форуму:
    function forum_open($forum_id, $user_id)
    {
    	$this->include_libs();
    	$role_id = 14; // Full access
    
    	$auth_admin = new auth_admin();
    	
    	$q = DB::query(Database::SELECT,
    		"SELECT o.auth_option, r.auth_setting
    		FROM `bazar_acl_roles_data` AS r, `bazar_acl_options` AS o
    		WHERE o.auth_option_id = r.auth_option_id AND r.role_id = :role_id")
    		->param(':role_id', $role_id)
    		->as_object()
    		->execute('forum');
    	
    	$auth_settings = array();
    	foreach ($q as $row)
    	{
    		if ($row->auth_option != 'f_')
    		{
    			$auth_settings[$row->auth_option] = $row->auth_setting;
    		}
    			
    	}
    	$auth_admin->acl_set('user', $forum_id, $user_id, $auth_settings, $role_id, true);
    }


    А напоследок я скажу


    Это плохой подход, т.к. нарушает элментарные принципы ООП. Глобальные переменные, глобальные функции, константы… В общем, так делать не надо. А как надо, хотелось бы услышать в комментариях. Когда уже появится нормальный механизм интеграции. Спасибо.

    Сервис работает здесь. Если зарегиться на сайте и форуме, то при логине на сайт будет идти и логин на форум.
    Оригинал статьи
    Поделиться публикацией

    Комментарии 19

      –1
      Спасибо! Где-нибудь уже работает на реальных проектах?
      p.s.
      Вот кто-нибудь собрался бы и подобное описание для Yii сделал бы.
      Понимаю, что это непростительная жажда халявы *стыдно*
        +4
        Вам мало в жизни геморроя от костыльных решений?
        +2
        А регистрация пользователей на форуме и сайте идет в одну БД или в разные?
        Ну, и выглядит это все конечно очень коряво…

        В идеале бы вообще не трогать файлы форума, а написать что-то свое, используя имеющуюся информацию о структуре ожидаемого объекта User (он ведь в phpBB тоже в сессии хранится?). эдакий конвертер между Ko3 и phpBB, позволяющий интерпретировать текущие данные авторизации в понятные для системы объекты. И подключаться только этот конвертер, а не половину «вражеского» приложения.
          0
          Регистрации в разных базах. Выглядит криво, ну а как по другому, phpBB сам нифига не объектно-ориентирован. Только конвертер (адаптер), и подключать его.
          +1
          У меня все допилы phpBB приводили к одной крайне неприятной проблеме — невозможности оперативно проапгрейдить движок до свежей версии.
            0
            Радует, что phpBB4 пишется на Symfony2. А пока да, такого рода извраты неизбежны.
              +1
              'user_timezone' => 10.00,
              'user_dst' => 1,
              'user_lang' => 'ru',

              Вот это лучше не в коде задавать, а получать значения из $config, дабы сохранить возможность настройки phpBB в администраторском разделе.
                –1
                Зачем это вообще нужно?!
                  +2
                  Я в свое время пошел по другому пути.
                  Форум положил на отдельный поддомен, он использовал другую БД. Для коханы написал адаптер который при добавлении/редактировании пользователя добавлял его еще и в базу форума.
                  При этом никаких конфликтов, два разных проекта запускались отдельно, их легко обновлять и поддерживать. Синхронизация только БД.
                  Единственный минус — нет общей авторизации, т.е. логин и пароль один, но авторизация отдельная.
                    0
                    Можно поподробней про адаптор?
                      0
                      Ну просто некторый объект с методами типа addUser(), editUser()
                      Пользователь регистрируется на сайте, мы добавляем его себе в базу, а потом объект юзера закидываем в метод Forum_Adapter::addUser($user), который добавляет строчку в форумной табличке. При изменении информации происходит тоже самое.
                      На форуме регистрация отключена и ссылаеся на регистрацию на сайте.
                        0
                        Также можно добавить методы типа getLastPosts и т.д. они просто будут коннектиться к форумной БД и брать инфу оттуда.
                      +1
                      class session // => class session_kohana
                      class user_kohana extends session_kohana // => class user_kohana extends session_kohana

                      опечатка? вроде должно быть

                      class user extends session // => class user_kohana extends session_kohana
                        0
                        Да, спасибо.
                        0
                        Логин и логаут можно подсунуть в includes/auth/auth_custom.php и выбрать в админке нужный метод аутентификации.
                        Региcтрацию можно сделать отдельно, меняя в шаблоне ссылку для регистрации на свой модуль.
                          +2
                          К сожалению, какое-бы решение тут не описали, все равно это будет костыль, какой-то больший, какой-то меньший, но все равно — костыль.
                          Разработчики форума phpBB постоянно наступают на те же грабли и такое впечатление, что вообще не слушают мнения комьюнити. Думаю, даже с phpBB4 ситуация будет не лучше.

                          Чтобы хоть как-то упростить интеграцию с phpBB, разработчикам самого phpBB нужно сделать несколько довольно простых вещей:

                          1. переименовать все классы, стили, функции так, чтобы они начинались с префикса phpbb_ (вот с таблицами в базе сделали ж, а чего про остальное забыли?).
                          2. сделать единый include, например, чтобы разработчик мог дернуть файл /forum/include.php и все API форума подключиться.
                          3. сделать единую систему инициализации. Если разработчик будет использовать IDE, то чтобы он набрал phpbb:: и нажал ctrl+space — и IDE подсказала, что дальше можно сделать. Тоесть, создать класс phpbb, который будет либо static, либо singleton, но именно с него все начинается.
                          4. И только тогда уже выносить методы типа PHPBB::Get()->getUserService()->login(login, pass);…

                          Т.е., по-простому, наконец нормально выучить и применить хотя-бы базовые основы ООП.

                          Хотя конечно, есть вариант SOAP API и т.п. — но толку от этого будет не много, если внутри архитектуры полный ппц.
                          0
                          Мы уже полтора года назад реализовали интеграцию phpBB3 и Codeigniter. Все это чудо прекрасно работает на сайте «Крокодил: игра в ассоциации».

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое