Ruby on Rails
PhpBB Integration

We have a lot of nice tools and tricks for user login/authentications in Rails so far, such as the LoginGenerator and the SaltedHashLoginGenerator.

Unfortunately, Ruby does not yet have a killer BB solution (though rForum looks promising). So, for many, phpBB is still the way to go. Unfortunately, if you want to make your site a single login site, we need a way to integrate our sessions with phpBB (after all, it sucks having 1 login for a site, and another for the forums for that same site).

The following describes what I’ve done so far in integrating my Rails site with my phpBB2 forums (work in progress, as I am in the process of making this integration now). Don’t worry, it’s not as hard as you think it might be :). Since we are using phpBB as our model for user management, we’ll have to ditch LoginGenerator and SaltedHashLoginGenerator.


First of all, the following assumes that you installed you phpBB2 forums in the same database as your Rails application, and that you use the default “phpbb_” prefix for your phpBB tables.

The two tables of most interest to us in the phpBB2 structure is phpbb_users and phpbb_sessions.

First, create a model for your User:

/path/to/rails/app> rails script/generate model User

And, in app/model/user.rb, place the following:


class User < ActiveRecord::Base
    set_primary_key "user_id" 
    set_table_name "phpbb_users" 

    def is_online? 
        tmp = User.find_by_sql( "SELECT session_user_id, session_logged_in FROM phpbb_sessions WHERE session_user_id=#{self.user_id}" )[0]
        if self.user_allow_viewonline == 1 && self.user_id != -1 && !tmp.nil? 
            return true
        else 
            return false
        end
    end
end

This is pretty basic right now (suggestions welcome!). Basically, we just tell ActiveRecord that our model is based on the table phpbb_users with a primary key of user_id (instead of the normal id). The method is_online?() is just a basic query for asking if a specific user is currently logged in (and returns true only if the user wants to be visible. Also, in phpBB the user with id == -1 is the anonymous user).

Now, phpBB stores cookies on your machine whenever you login. Fortunately, the Rails “cookies” hash can read this just as easy as it would a Rails cookie.

With that in mind, we can detect which phpBB user, if any, is logged in (you may need to modify the cookies[“phpbb2mysql_sid”] if you customized your cookie naming at all in phpbb).

In my app/controllers/application.rb, I put:


class ApplicationController < ActionController::Base

    def get_user_by_sid( )
        sid = cookies["phpbb2mysql_sid"]
        return nil if sid.nil?
        User.find_by_sql( [ "SELECT phpbb_users.* FROM phpbb_sessions,phpbb_users WHERE session_id = ? AND phpbb_sessions.session_user_id = phpbb_users.user_id", sid ] )[0]
    end

end

What this method does is return a User object based on the phpbb session data. If the user logs off of phpBB, they are logged off the Rails app.

So, any controller and any action in which you are wanting to verify which (if any) user is logged in, you can simply call get_user_by_sid() and use the returned User object.

With this, you are now ready to create any relationships between the phpbb user and the rest of your rails app. The user only has to login once, and you have access to all their phpBB information (email, AIM, avatar, etc), and any of your Rails User specific data.

If you want to allow a user to login without making them go to the forums, then you can use the following php script. I have not figured out how to do this in Ruby yet, so I put this .php in my public/So it seems all the php gibberish I had in there wasn’t necessary. All you need is the following form. Pay particular note to the

<input type=hidden name="redirect" value="..">
, as this tells phpBB where to redirect after the login (so they can login in your app and never even have to see the forums) note, this path is relative to the location of your forums.


<table border=0>
<tr><td colspan=2 align=left>
       <B>Please Login</b>
   </td>
</tr>
<tr>
   <td>
       Username:<form action="/forums/login.php" method="post" name="login_form" target=_top>
   </td>
   <td valign=top>
       <input type="text" name="username">
   </td>
</tr>
<tr>
   <td>Password:</td>
   <td>
       <input type="password" name="password">
   </td>
<tr>
   <td></td>
   <td align=center>
       <input type=hidden name="redirect" value=".."><input type="submit" value="Login" name="login"></form>
   </td>
</tr>
<tr>
   <td></td>
   <td align=center>
       <a href="http://www.yourdomain.com/forums/profile.php?mode=register" target=_top><span style="font-size: 0.8em">Register</span></a>
   </td>
</tr>
</table>

To log out of the system without a trip to the forums, you would use:


<form action="/forums/login.php?logout=true" method="post" name="login_form" target=_top>
  <input type=hidden name="redirect" value="..">(<a href="#" onClick="javascript:document.login_form.submit()">logout</a>
</form>

That’s all for now. Obviously, the User model is fairly bare. It should be enough to get you going though. And I’m not sure I followed Rails “best practices” (I’m still fairly new to the game). Suggestions are welcome.


I’m in the process of creating a method which will actually make a post on the forums. Anyone know how to have an action make a POST?

The following is how I am able to post to the forums from within the rails app. I use this for autoposting to the forums whenever I have a news item on the Rails app.

First of all, we need to add a few models:


# app/model/phpbb_forum.rb
class PhpbbForum < ActiveRecord::Base
    set_primary_key :forum_id
end

# app/model/phpbb_post.rb
class PhpbbPost< ActiveRecord::Base
    set_primary_key :post_id
end

# app/model/phpbb_posts_text.rb
class PhpbbPostsText < ActiveRecord::Base
    set_primary_key :posts_text_id
    set_table_name :phpbb_posts_text
end

# app/model/phpbb_topic.rb
class PhpbbTopic < ActiveRecord::Base
    set_primary_key :topic_id
    belongs_to :user, :foreign_key => 'topic_poster'
end

Next, add the following function to your app (I put it in app/controllers/application.rb so I can use it in all controllers):


def post_topic( forum_id, user_id, subject="test subject", content="test content" )
        forum = PhpbbForum.find( forum_id )
        return if( forum.nil? || user_id.nil?  || forum_id.nil? || subject.nil? || content.nil?  )
        return unless validate_admin_with_hidden_action()
        # Apply post
        # Update forums table with new posts count, new topic count, and forum_last_post_id
        forum.forum_posts += 1
        forum.forum_topics += 1
        # Update user post count
        user.user_posts += 1
        user.save
        # Update posts with new topic_id,
        topic = PhpbbTopic.create
        post = PhpbbPost.create
        post.topic_id = topic.id
        post.forum_id = forum.id
        post.poster_id = user.id
        post.post_time = Time.now.to_f.to_i
        post.poster_id = user_id
        post.save
        # Create new topic
        topic.forum_id = forum.id
        topic.topic_title = subject
        topic.topic_poster = user_id
        topic.topic_time = Time.now.to_f.to_i
        topic.topic_first_post_id = post.id
        topic.topic_last_post_id = post.id
        topic.save
        forum.forum_last_post_id = post.id
        forum.save
        # Create new posts text
        post_text = PhpbbPostsText.new
        post_text.post_id = post.id
        post_text.bbcode_uid = "3edc9030fc" 
        post_text.post_subject = subject
        post_text.post_text = content
        post_text.save
        return topic.id
    end
end

Yes, I was very verbose above, and one could easily shorten this by passing a hash on the Model.new, but I wanted to make it clear what is going on here.

So far, the above solution has worked for me … but be warned, this has not been thoroughly tested and may throw your forums out of whack.


Extras

Here is some additional functionality that might be useful, such as getting the last post from a user, etc.

First, the model code (in their respective files):


class PhpbbForum < ActiveRecord::Base
    set_primary_key :forum_id
    has_many :phpbb_topics, :foreign_key => 'forum_id'
end
class PhpbbPost< ActiveRecord::Base
    set_primary_key :post_id
    belongs_to :phpbb_topic, :foreign_key => 'topic_id'
    belongs_to :phpbb_forum, :foreign_key => 'forum_id'
    belongs_to :user, :foreign_key => 'poster_id'
    has_one :phpbb_posts_text, :foreign_key => 'post_id'

    def text
        self.phpbb_posts_text.text
    end
end

class PhpbbPostsText < ActiveRecord::Base
    set_primary_key :post_id
    set_table_name :phpbb_posts_text
    belongs_to :phpbb_posts, :foreign_key=>'post_id'

   def text
       self[:post_text]
   end
end

class PhpbbSession < ActiveRecord::Base
    set_primary_key :session_id
    belongs_to :user, :foreign_key => 'session_user_id'
end

class PhpbbTopic < ActiveRecord::Base
    set_primary_key :topic_id
    belongs_to :user, :foreign_key => 'topic_poster'
    has_many :phpbb_posts

    def last_post
        PhpbbPost.find( self.topic_last_post_id )
    end
end

With a user model that looks like this:

class User < ActiveRecord::Base
    set_primary_key "user_id" 
    set_table_name :phpbb_users

    has_many :phpbb_posts, :foreign_key => 'poster_id', :order=>'post_time DESC'
    has_one  :phpbb_session, :foreign_key=>'session_user_id'

    def is_online? 
        tmp = User.find_by_sql( "SELECT session_user_id, session_logged_in FROM phpbb_sessions WHERE session_user_id=#{self.user_id}" )[0]
        if self.user_allow_viewonline == 1 && self.user_id != -1 && !tmp.nil? 
            return true
        else 
            return false
        end
    end

    def sid
        return self.phpbb_session.nil? ? -1 : self.phpbb_session.session_id
    end

    # Grab latest post for this user
    def latest_post
        self.phpbb_posts.first
    end

end

Now you can do things like:

Post.find( 1 ).text
Post.find( 1 ).user
Post.find( 1 ).topic
Post.find( 1 ).forum
User.find( 2 ).last_post.text

etc.