railpage/railpagecore

View on GitHub
lib/Forums/Forums.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

/**
 * Forums API
 * @since Version 3.0.1
 * @version 3.9
 * @package Railpage
 * @author James Morgan, Michael Greenhill
 */
 
namespace Railpage\Forums;

use Railpage\Users\User;
use Railpage\Forums\Post;
use Railpage\Forums\Forums;
use Railpage\AppCore;
use Railpage\Debug;
use Railpage\Module;
use Exception;
use DateTime;
use DateTimeZone;
use DateInterval;
use stdClass;
use Zend_Acl;
use Zend_Acl_Resource;
use Zend_Acl_Role;
use ReflectionClass;
use Railpage\Registry;

define("RP_THREAD_LOCKED", 1);
define("RP_THREAD_UNLOCKED", 0);
 
/**
 * phpBB base class
 * @since Version 3.2
 * @version 3.2
 * @author Michael Greenhill
 */
 
class Forums extends AppCore {
    
    /**
     * Auth: list all
     * @const AUTH_LIST_ALL
     */
    
    const AUTH_LIST_ALL = 0;
    
    /**
     * Auth: All 
     * @const AUTH_ALL
     */
    
    const AUTH_ALL = 0;
    
    /**
     * Auth: Registered users
     * @const AUTH_REG
     */
    
    const AUTH_REG = 1;
    
    /**
     * Auth: ACL
     * @const AUTH_ACL
     */
    
    const AUTH_ACL = 2;
    
    /**
     * Auth: Moderator
     * @const AUTH_MOD
     */
    
    const AUTH_MOD = 3;
    
    /**
     * Auth: Administrator
     * @const AUTH_ADMIN
     */
     
    const AUTH_ADMIN = 5;
    
    /**
     * Auth: View
     * @const AUTH_VIEW
     */
    
    const AUTH_VIEW = 1;
    
    /**
     * Auth: Read
     * @const AUTH_READ
     */
    
    const AUTH_READ = 2;
    
    /**
     * Auth: Create a thread
     * @const AUTH_POST
     */
    
    const AUTH_POST = 3;
    
    /**
     * Auth: Reply to a thread
     * @const AUTH_REPLY
     */
    
    const AUTH_REPLY = 4;
    
    /**
     * Auth: Edit a post
     * @const AUTH_EDIT
     */
    
    const AUTH_EDIT = 5;
    
    /**
     * Auth: Delete a post
     * @const AUTH_DELETE
     */
    
    const AUTH_DELETE = 6;
    
    /**
     * Auth: Create an announcement
     * @const AUTH_ANNOUNCE
     */
    
    const AUTH_ANNOUNCE = 7;
    
    /**
     * Auth: Create a sticky
     * @const AUTH_STICKY
     */
    
    const AUTH_STICKY = 8;
    
    /**
     * Auth: Create a poll
     * @deprecated Since Version 3.5
     * @const AUTH_POLLCREATE
     */
    
    const AUTH_POLLCREATE = 9;
    
    /**
     * Auth: Vote in a poll
     * @deprecated Since Version 3.5
     * @const AUTH_VOTE
     */
    
    const AUTH_VOTE = 10;
    
    /**
     * Auth: attach
     * @const AUTH_ATTACH
     */
    
    const AUTH_ATTACH = 11;
    
    /**
     * Memcache object
     * @since Version 3.2
     * @version 3.2
     * @var object $memcache
     */
    
    public $memcache;
    
    /**
     * Permissions object
     * @since Version 3.2
     * @version 3.2
     * @var object $permissions
     */
    
    public $permssions;
    
    /**
     * Forum list
     * @since Version 3.2
     * @version 3.2
     * @var array $forums
     */
    
    public $forums;
    
    /**
     * Post BBCode UID
     * @since Version 3.0.1
     * @version 3.0.1
     * @var string $bbcodeuid
     */
    
    public $bbcodeuid = 'sausages';
    
    /**
     * Topics this user has read
     * @since Version 3.2
     * @var array $read_topics
     */
    
    public $read_topics;
    
    /**
     * Constructor
     * @since Version 3.8.7
     */
    
    public function __construct() {
        
        parent::__construct(); 
        
        $this->Module = new Module("forums");
        
        $this->Templates = new stdClass;
        $this->Templates->watchedthreads = "thread.watching";
    }
    
    /**
     * Get ranks from database
     * @since Version 3.2
     * @version 3.2
     * @return array
     */
    
    public function ranks() {
        $query = "SELECT * FROM nuke_bbranks WHERE rank_special = 0";
            
        if ($this->db instanceof \sql_db) {
            if ($rs = $this->db->query($query)) {
                $return = array(); 
                
                while ($row = $rs->fetch_assoc()) {
                    $return[] = $row;
                }
                
                return $return;
            } else {
                trigger_error("PhpBB Base : Unable to fetch standard ranks from database");
                trigger_error($this->db->error);
                
                return false;
            }
        } else {
            return $this->db->fetchAll($query); 
        }
    }
    
    /**
     * Latest forum posts
     * @since Version 3.2
     * @version 3.2
     * @param array $auth_ary
     * @param int $since
     * @return array
     */
    
    public function latestPosts($is_auth_ary, $since, $limit = 10) {
        if (empty($is_auth_ary) || !is_array($is_auth_ary)) {
            return false;
        }
        
        // To start off, get the list of forums
        $Index      = new Index($this->db);
        $forum_list = $Index->forums();
        
        $exclude_forums = array();
        
        for ($i = 0, $c = count($forum_list); $i < $c; $i++) {
            if ($is_auth_ary[$forum_list[$i]['forum_id']]['auth_view']) {
                if (!in_array($forum_list[$i]['forum_id'], array("37"))) {
                    $exclude_forums[] = $forum_list[$i]['forum_id'];
                }
            }
        }
        
        if ($this->db instanceof \sql_db) {
            if (!empty($since) && is_int($since) && $since > 0) {
                $since = " AND p.post_time >= ".$this->db->real_escape_string(intval($since));
            }
            
            $query = "SELECT p.post_id, pt.bbcode_uid, p.post_time, pt.post_text, p.enable_bbcode, p.enable_html, p.enable_smilies, p.enable_sig, p.post_edit_time, p.poster_id AS user_id, u.username, t.topic_title, t.topic_id, f.forum_name, f.forum_id
                        FROM nuke_bbposts p
                        LEFT JOIN nuke_bbposts_text AS pt ON p.post_id = pt.post_id
                        LEFT JOIN nuke_users AS u ON p.poster_id = u.user_id
                        LEFT JOIN nuke_bbtopics AS t ON p.topic_id = t.topic_id
                        LEFT JOIN nuke_bbforums AS f ON p.forum_id = f.forum_id
                        WHERE p.forum_id IN (".implode(",", $exclude_forums).") ".$since."
                        ORDER BY p.post_time DESC
                        LIMIT 0, ".$this->db->real_escape_string($limit)."";
                        
            if ($rs = $this->db->query($query)) {
                $return = array(); 
                
                while ($row = $rs->fetch_assoc()) {
                    $row['post_text'] = stripslashes($row['post_text']);
                    $return[] = $row;
                }
                
                return $return;
            } else {
                trigger_error("Could not fetch new posts");
                trigger_error($this->db->error);
                return false;
            }
        } else {
            $params = array(); 
            
            if (!empty($since) && is_int($since) && $since > 0) {
                $since = " AND p.post_time >= ?";
                $params[] = intval($since); 
            }
            
            $query = "SELECT p.post_id, pt.bbcode_uid, p.post_time, pt.post_text, p.enable_bbcode, p.enable_html, p.enable_smilies, p.enable_sig, p.post_edit_time, p.poster_id AS user_id, u.username, t.topic_title, t.topic_id, f.forum_name, f.forum_id
                        FROM nuke_bbposts p
                        LEFT JOIN nuke_bbposts_text AS pt ON p.post_id = pt.post_id
                        LEFT JOIN nuke_users AS u ON p.poster_id = u.user_id
                        LEFT JOIN nuke_bbtopics AS t ON p.topic_id = t.topic_id
                        LEFT JOIN nuke_bbforums AS f ON p.forum_id = f.forum_id
                        WHERE p.forum_id IN (".implode(",", $exclude_forums).") ".$since."
                        ORDER BY p.post_time DESC
                        LIMIT 0, ?";
            
            $params[] = $limit; 
            
            $return = array();
            
            foreach ($this->db->fetchAll($query, $params) as $row) {
                $row['post_text'] = stripslashes($row['post_text']);
                $return[] = $row;
            }
            
            return $return;
        }
    }
    
    /**
     * Load the topics this user has read (stored in a cookie)
     * @since Version 3.2
     * @version 3.2
     * @param int $user_id
     */
    
    public function getReadTopics($user_id = false) {
        global $Error;
        
        if (!$user_id) {
            return false;
        }
        
        if ($this->db instanceof \sql_db) {
            // Get read topics per user
            $query = "SELECT vt.topic_id, vt.time, t.forum_id, t.topic_last_post_id AS last_post_id, p.post_time AS last_post_time 
                        FROM viewed_threads AS vt
                        LEFT JOIN nuke_bbtopics AS t ON vt.topic_id = t.topic_id
                        LEFT JOIN nuke_bbposts AS p ON t.topic_last_post_id = p.post_id
                        WHERE vt.user_id = '".$this->db->real_escape_string($user_id)."'";
            
            $topics = array(); 
            
            if ($rs = $this->db->query($query)) {
                while ($row = $rs->fetch_assoc()) {
                    $topics[$row['forum_id']]['topics'][$row['topic_id']] = $row;
                }
            
                // Set the last viewed forum time
                foreach ($topics as $forum_id => $data) {
                    foreach ($data['topics'] as $topic_id => $array) {
                        if ($array['last_post_time'] > $topics[$forum_id]['last_viewed']) {
                            $topics[$forum_id]['last_viewed'] = $array['last_post_time'];
                        }
                    }
                }
                
                return true;
            } else {
                return false;
            }
        } else {
            $query = "SELECT vt.topic_id, vt.time, t.forum_id, t.topic_last_post_id AS last_post_id, p.post_time AS last_post_time 
                        FROM viewed_threads AS vt
                        LEFT JOIN nuke_bbtopics AS t ON vt.topic_id = t.topic_id
                        LEFT JOIN nuke_bbposts AS p ON t.topic_last_post_id = p.post_id
                        WHERE vt.user_id = ?";
            
            $topics = array(); 
            
            foreach ($this->db->fetchAll($query, $user_id) as $row) {
                $topics[$row['forum_id']]['topics'][$row['topic_id']] = $row;
            }
            
            // Set the last viewed forum time
            foreach ($topics as $forum_id => $data) {
                foreach ($data['topics'] as $topic_id => $array) {
                    if (!isset($topics[$forum_id]['last_viewed']) || $array['last_post_time'] > $topics[$forum_id]['last_viewed']) {
                        $topics[$forum_id]['last_viewed'] = $array['last_post_time'];
                    }
                }
            }
            
            return $topics;
        }
    }
    
    /**
     * New forum posts
     * @since Version 3.2
     * @version 3.2
     * @return array
     * @param int $from_time
     */
    
    public function newPosts($forums = false, $from_time = false, $items_per_page = 25, $start = 0) {
        if (!$forums || !is_array($forums)) {
            throw new \Exception("You must provide a list of forums this user has permission to view");
            return false;
        }
        
        if ($this->db instanceof \sql_db) {
            $query = "SELECT SQL_CALC_FOUND_ROWS p.post_time, pt.bbcode_uid, pt.post_subject, pt.post_text, u.username, u.user_id, t.topic_title, t.topic_id, t.topic_views, t.topic_replies, f.forum_name, f.forum_id, ufirst.user_id AS first_user_id, ufirst.username AS first_username, pfirst.post_id AS first_post_id, pfirst.post_time AS first_post_time
                        FROM nuke_bbposts AS p
                        LEFT JOIN nuke_bbposts_text AS pt ON p.post_id = pt.post_id
                        LEFT JOIN nuke_users AS u ON u.user_id = p.poster_id
                        LEFT JOIN nuke_bbtopics AS t ON p.topic_id = t.topic_id
                        LEFT JOIN nuke_bbforums AS f ON f.forum_id = p.forum_id
                        LEFT JOIN nuke_bbposts AS pfirst ON pfirst.post_id = t.topic_first_post_id
                        LEFT JOIN nuke_users AS ufirst ON ufirst.user_id = pfirst.poster_id
                        WHERE p.forum_id IN (".implode(",", $forums).")";
            
            if ($from_time) { 
                $query .= " AND p.post_time >= ".$from_time; 
            } 
            
            // Group by
            $query .= " GROUP BY t.topic_id ORDER BY p.post_time DESC";
            
            $query .= " LIMIT ".$start.", ".$items_per_page."";
            
            if ($rs = $this->db->query($query)) {
                $total = $this->db->query("SELECT FOUND_ROWS() AS total"); 
                $total = $total->fetch_assoc(); 
                
                $return['total'] = $total['total']; 
                
                $posts = array(); 
                while ($row = $rs->fetch_assoc()) {
                    $return['topics'][$row['topic_id']][] = $row; 
                }
            } else {
                throw new \Exception($this->db->error); 
                return false;
            } 
            
            return $return; 
        } else {
            $query = "SELECT SQL_CALC_FOUND_ROWS p.post_time, pt.bbcode_uid, pt.post_subject, pt.post_text, u.username, u.user_id, t.topic_title, t.topic_id, t.topic_views, t.topic_replies, f.forum_name, f.forum_id, ufirst.user_id AS first_user_id, ufirst.username AS first_username, pfirst.post_id AS first_post_id, pfirst.post_time AS first_post_time
                        FROM nuke_bbposts AS p
                        LEFT JOIN nuke_bbposts_text AS pt ON p.post_id = pt.post_id
                        LEFT JOIN nuke_users AS u ON u.user_id = p.poster_id
                        LEFT JOIN nuke_bbtopics AS t ON p.topic_id = t.topic_id
                        LEFT JOIN nuke_bbforums AS f ON f.forum_id = p.forum_id
                        LEFT JOIN nuke_bbposts AS pfirst ON pfirst.post_id = t.topic_first_post_id
                        LEFT JOIN nuke_users AS ufirst ON ufirst.user_id = pfirst.poster_id
                        WHERE p.forum_id IN (".implode(",", $forums).")";
            
            $params = array(); 
            
            if ($from_time) { 
                $query .= " AND p.post_time >= ?"; 
                $params[] = $from_time;
            } 
            
            // Group by
            $query .= " GROUP BY t.topic_id ORDER BY p.post_time DESC LIMIT ?, ?";
            $params[] = $start; 
            $params[] = $items_per_page; 
            
            $result = $this->db->fetchAll($query, $params); 
            $return = array(); 
            $return['total'] = $this->db->fetchOne("SELECT FOUND_ROWS() AS total"); 
            
            foreach ($result as $row) {
                $return['topics'][$row['topic_id']][] = $row; 
            }
            
            return $return;
        }
    }
    
    /**
     * Get forum categories
     * @since Version 3.8.7
     * @yield new Category
     */
     
    public function getCategories() {
        $query = "SELECT cat_id FROM nuke_bbcategories ORDER BY cat_order";
        
        foreach ($this->db->fetchAll($query) as $row) {
            yield new Category($row['cat_id']);
        }
    }
    
    /**
     * Build the Forums ACL
     * @since Version 3.8.7
     * @param boolean $force Force an update of the ACL
     * @todo Finish this shit
     */
    
    public function buildACL($force = false) {
        
        $Registry = Registry::getInstance(); 
        
        try {
            $ForumsACL = $Registry->get("forumsacl"); 
            $this->ZendACL = $ForumsACL;
            return;
            
        } catch (Exception $e) {
            // Fook it
        }
        
        Debug::RecordInstance(__METHOD__); 
        $timer = Debug::getTimer(); 
        
        $acl = $Registry->get("acl"); 
        
        if (!$this->User instanceof User) {
            throw new Exception("A valid user must be set before the ACL can be built");
        }
        
        $mckey = "railpage.forums.list";
        
        if ($force || !$forums = $this->Memcached->fetch($mckey)) {
            $query = "SELECT forum_id FROM nuke_bbforums";
            
            $forums = $this->db->fetchAll($query);
            
            $this->Memcached->save($mckey, $forums);
        }
        
        $acl_forums = array();
        
        /**
         * Add all the forums to the ACL
         */
        
        foreach ($forums as $row) {
            $acl_forum_name = sprintf("railpage.forums.forum:%d", $row['forum_id']);
            $acl_forums[$row['forum_id']] = $acl_forum_name;
            
            try {
                $acl->get($acl_forum_name);
            } catch (Exception $e) {
                $acl->addResource(new Zend_Acl_Resource($acl_forum_name));
            }
        }
        
        /**
         * Get the forum permissions from the database
         */
        
        $a_sql = array("auth_view", "auth_read", "auth_post", "auth_reply", "auth_edit", "auth_delete", "auth_sticky", "auth_announce", "auth_vote", "auth_pollcreate");
        $auth_fields = array('auth_view', 'auth_read', 'auth_post', 'auth_reply', 'auth_edit', 'auth_delete', 'auth_sticky', 'auth_announce', 'auth_vote', 'auth_pollcreate');
        
        $query = "SELECT forum_id, " . implode(", ", $a_sql) . ", " . self::AUTH_ACL . " AS auth_mod FROM nuke_bbforums";
        
        $db_acl = array(); 
        
        foreach ($this->db->fetchAll($query) as $row) {
            $db_acl[$row['forum_id']] = $row;
        }
        
        /**
         * Get the group permissions for this user
         */
        
        $query = "SELECT a.* FROM nuke_bbauth_access AS a WHERE a.group_id IN (SELECT group_id FROM nuke_bbuser_group WHERE user_id = ? AND user_pending = 0)";
        $gperms = array(); 
        
        foreach ($this->db->fetchAll($query, $this->User->id) as $perm) {
            $forum_id = $perm['forum_id'];
            $group_id = $perm['group_id'];
            
            unset($perm['forum_id']);
            unset($perm['group_id']);
            
            $gperms[$forum_id][$group_id] = $perm;
        }
        
        /**
         * Guest details
         */
        
        $guestfucknamingthis = [
            self::AUTH_MOD => $this->User->inGroup(RP_GROUP_MODERATORS),
            self::AUTH_ADMIN => $this->User->inGroup(RP_GROUP_ADMINS)
        ];
        
        /**
         * Add the forum permissions to Zend_ACL
         */
        
        foreach ($db_acl as $forum_id => $permissions) {
            $allowed = array(); 
            $denied = array();
            
            unset($permissions['forum_id']);
            
            $allowed = array_merge($allowed, array_keys($permissions, self::AUTH_ALL)); 
            
            if (!$this->User->guest) {
                $allowed = array_merge($allowed, array_keys($permissions, self::AUTH_REG)); 
            }
            
            if ($guestfucknamingthis[self::AUTH_MOD]) {
                $allowed = array_merge($allowed, array_keys($permissions, self::AUTH_MOD)); 
            }
            
            if ($guestfucknamingthis[self::AUTH_ADMIN]) {
                $allowed = array_merge($allowed, array_keys($permissions, self::AUTH_ADMIN)); 
            }
            
            $perms_acl = array_keys($permissions, self::AUTH_ACL);
            
            if (count($perms_acl)) {
                if (isset($gperms[$forum_id])) {
                    foreach ($gperms[$forum_id] as $group) {
                        foreach ($group as $gitem => $gval) {
                            $allowed = array_merge($allowed, array_keys($permissions, self::AUTH_REG)); 
                            
                            if ($guestfucknamingthis[self::AUTH_MOD]) {
                                $allowed = array_merge($allowed, array_keys($permissions, self::AUTH_MOD)); 
                            }
                            
                            if ($guestfucknamingthis[self::AUTH_ADMIN]) {
                                $allowed = array_merge($allowed, array_keys($permissions, self::AUTH_ADMIN)); 
                            }
                        }
                    }
                }
            }
            
            $allowed = array_unique($allowed);
            
            #continue;
            
            /*
            foreach ($permissions as $item => $value) {
                switch ($value) {
                    
                    case self::AUTH_ACL . "zzz" :
                        if (isset($gperms[$forum_id])) {
                            foreach ($gperms[$forum_id] as $group) {
                                foreach ($group as $gitem => $gval) {
                                    switch ($gval) {
                                        case self::AUTH_REG :
                                            $allowed[] = $item;
                                            break;
                                        
                                        case self::AUTH_ACL :
                                            // Inception
                                            break;
                                        
                                        case self::AUTH_MOD :
                                            if ($this->User->inGroup(RP_GROUP_MODERATORS)) {
                                                $allowed[] = $gitem;
                                            }
                                            break;
                                        
                                        case self::AUTH_ADMIN :
                                            if ($this->User->inGroup(RP_GROUP_ADMINS)) {
                                                $allowed[] = $gitem;
                                            }
                                            
                                            break;
                                    }
                                }
                            }
                        }
                        break;
                    
                    case self::AUTH_MOD  . "zzz": 
                        if ($this->User->inGroup(RP_GROUP_MODERATORS)) {
                            $allowed[] = $item;
                        }
                        break;
                    
                    case self::AUTH_ADMIN . "zzz" :
                        if ($this->User->inGroup(RP_GROUP_ADMINS)) {
                            $allowed[] = $item;
                        }
                        break;
                }
            }
            */
            
            foreach ($permissions as $item => $value) {
                if (!in_array($item, $allowed)) {
                    $denied[] = $item;
                }
            }
            
            #$allowed = array_unique($allowed);
            #$denied = array_unique($denied);
            
            $acl->allow("forums_viewer", sprintf("railpage.forums.forum:%d", $forum_id), $allowed);
            $acl->deny("forums_viewer", sprintf("railpage.forums.forum:%d", $forum_id), $denied);
        }
        
        $Registry->set("acl", $acl); 
        $Registry->set("forumsacl", $acl); 
        $this->ZendACL = $acl;
        
        Debug::LogEvent(__METHOD__, $timer); 
        
        return;
        
    }
    
    /**
     * Return the updated ACL
     * @since Version 3.8.7
     * @return \Zend_Acl
     */
    
    public function getACL() {
        if (!isset($this->ZendACL) || !$this->ZendACL instanceof Zend_Acl) {
            $this->buildACL(); 
        }
        
        return $this->ZendACL;
    }
    
    /**
     * Get all forums
     * @since Version 3.9
     * @return array
     */
    
    public function getAllForums() {
        $query = "SELECT f.*, c.*, lp.topic_id AS lastpost_topic_id, lp.poster_id AS lastpost_user_id, u.username AS lastpost_username, lp.post_time AS lastpost_time 
                    FROM nuke_bbforums AS f
                    LEFT JOIN nuke_bbposts AS lp ON lp.post_id = forum_last_post_id
                    LEFT JOIN nuke_bbcategories AS c ON c.cat_id = f.cat_id
                    LEFT JOIN nuke_users AS u ON u.user_id = lp.poster_id
                    ORDER BY c.cat_order, f.forum_order";
        
        $return = array(
            "categories" => array()
        ); 
        
        foreach ($this->db->fetchAll($query) as $row) {
            if (!isset($return['categories'][$row['cat_id']])) {
                $return['categories'][$row['cat_id']] = array(
                    "id" => $row['cat_id'],
                    "url" => sprintf("/f-c%d.htm", $row['cat_id']),
                    "name" => $row['cat_title'],
                    "order" => $row['cat_order'],
                    "forums" => array()
                );
            }
            
            $Date = new DateTime(sprintf("@%d", $row['lastpost_time']));
            
            $return['categories'][$row['cat_id']]['forums'][$row['forum_id']] = array(
                "id" => $row['forum_id'],
                "url" => sprintf("/f-f%d.htm", $row['forum_id']),
                "name" => $row['forum_name'],
                "desc" => $row['forum_desc'],
                "status" => $row['forum_status'],
                "order" => $row['forum_order'],
                "posts" => $row['forum_posts'],
                "topics" => $row['forum_topics'],
                "lastpost" => array(
                    "date" => array(
                        "timestamp" => $Date->getTimestamp(),
                        "absolute" => $Date->format("Y-m-d H:i:s"),
                        "relative" => time2str($Date->getTimestamp())
                    ),
                    "id" => $row['forum_last_post_id'],
                    "url" => sprintf("/f-p%d.htm#%d", $row['forum_last_post_id'], $row['forum_last_post_id']),
                    "thread" => array(
                        "id" => $row['lastpost_topic_id'],
                        "url" => sprintf("/f-t%d.htm", $row['lastpost_topic_id'])
                    ),
                    "author" => array(
                        "id" => $row['lastpost_user_id'],
                        "username" => $row['lastpost_username'], 
                        "url" => sprintf("/user/%d", $row['lastpost_user_id'])
                    )
                ),
                "prune" => array(
                    "enabled" => (bool) $row['prune_enable'],
                    "next" => $row['prune_next']
                ),
                "permissions" => array(
                    "view" => $row['auth_view'],
                    "read" => $row['auth_read'],
                    "post" => $row['auth_post'],
                    "reply" => $row['auth_reply'],
                    "edit" => $row['auth_edit'],
                    "delete" => $row['auth_delete'],
                    "sticky" => $row['auth_sticky'],
                    "announce" => $row['auth_announce'],
                    "vote" => $row['auth_vote'],
                    "pollcreate" => $row['auth_pollcreate'],
                    "attachments" => $row['auth_attachments'],
                ),
                "parent" => array(
                    "id" => $row['forum_parent']
                )
            );
        }
        
        return $return;
    }
    
    /**
     * Get forum reputation names/values
     * @since Version 3.9
     * @return array
     */
    
    public static function getReputationTypes() {
        $reps = array(
            1 => array(
                "id" => 1,
                "name" => "Like",
                "icon" => "<span class='glyphicon glyphicon-thumbs-up'></span>",
                "count" => 0
            ),
            2 => array(
                "id" => 2,
                "name" => "Informative",
                "icon" => "<span class='glyphicon glyphicon-info-sign'></span>",
                "count" => 0
            ),
            3 => array(
                "id" => 3,
                "name" => "Helpful",
                "icon" => "<span class='glyphicon glyphicon-flash'></span>",
                "count" => 0
            ),
            4 => array(
                "id" => 4,
                "name" => "Funny",
                "icon" => "<span class='glyphicon glyphicon-heart-empty'></span>",
                "count" => 0
            ),
            5 => array(
                "id" => 5,
                "name" => "Agree",
                "icon" => "<span class='glyphicon glyphicon-ok'></span>",
                "count" => 0
            ),
            6 => array(
                "id" => 6,
                "name" => "Disagree",
                "icon" => "<span class='glyphicon glyphicon-remove'></span>",
                "count" => 0
            ),
        );
        
        return $reps;
    }
    
    /**
     * Get latest post ratings/reputation rankings
     * @since Version 3.9.1
     * @return array
     * @param int $limit Number of results to return
     */
    
    public function getLatestPostRatings($limit = 10) {
        $query = "SELECT r.*, u.username, t.topic_title, f.forum_name, p.poster_id
                    FROM nuke_bbposts_reputation AS r 
                        LEFT JOIN nuke_users AS u ON r.user_id = u.user_id 
                        LEFT JOIN nuke_bbposts AS p ON r.post_id = p.post_id
                        LEFT JOIN nuke_bbtopics AS t ON p.topic_id = t.topic_id
                        LEFT JOIN nuke_bbforums AS f ON p.forum_id = f.forum_id
                    ORDER BY r.date DESC
                    LIMIT 0, ?";
        
        $ratings = array();
        
        $reputationtypes = $this->getReputationTypes(); 
        
        foreach ($this->db->fetchAll($query, $limit) as $row) {
            $Date = new DateTime($row['date'], new DateTimeZone($this->User->timezone));
            
            $row['date'] = array(
                "absolute" => $Date->format($this->User->date_format),
                "relative" => time2str($Date->getTimestamp())
            );
            
            $row['type'] = $reputationtypes[$row['type']];
            
            $ThisUser = new User($row['user_id']);
            $row['user'] = array(
                "id" => $ThisUser->id,
                "username" => $ThisUser->username,
                "url" => $ThisUser->url->getURLs()
            );
            
            $ThisUser = new User($row['poster_id']);
            $row['author'] = array(
                "id" => $ThisUser->id,
                "username" => $ThisUser->username,
                "url" => $ThisUser->url->getURLs()
            );
            
            $Post = new Post($row['post_id']);
            
            $row['post'] = array(
                "id" => $Post->id,
                "url" => $Post->url->getURLs()
            );
            
            $row['thread'] = array(
                "id" => $Post->thread->id,
                "title" => $Post->thread->title,
                "url" => $Post->thread->url->getURLs()
            );
            
            $row['forum'] = array(
                "id" => $Post->thread->forum->id,
                "name" => $Post->thread->forum->name,
                "url" => $Post->thread->forum->url->getURLs()
            );
            
            $ratings[] = $row;
        }
        
        return $ratings;
    }
    
    /**
     * Get number of ratings per user in the last six months
     * @since Version 3.9.1
     * @var int $limit Maximum number of results to return
     * @return array
     */
    
    public function getMostUserRatings($limit = 10) {
        $query = "SELECT r.*, COUNT(r.user_id) AS count, u.username FROM nuke_bbposts_reputation AS r LEFT JOIN nuke_users AS u ON r.user_id = u.user_id WHERE r.date >= DATE_SUB(NOW(), INTERVAL 6 MONTH) GROUP BY r.user_id ORDER BY count DESC LIMIT 0, ?";
        
        return $this->db->fetchAll($query, $limit);
    }
    
    /**
     * Get forums that this user is allowed to x
     * @since Version 3.9.1
     * @return array
     * @param \Railpage\Users\User $User
     */
    
    public function getAllowedForumsIDsForUser(User $User, $permission = self::AUTH_ALL) {
        $acl = $this->setUser($User)->getACL(); 
        
        $ids = array(); 
        $allforums = $this->getAllForums();
        
        foreach ($allforums['categories'] as $category) {
            foreach ($category['forums'] as $forum) {
                if ($acl->isAllowed("forums_viewer", sprintf("railpage.forums.forum:%d", $forum['id']), $permission)) {
                    $ids[] = $forum['id'];
                }
            }
        }
        
        return $ids;
    }
    
    /**
     * Get unread forum posts for the given user
     * @since Version 3.9.1
     * @param \Railpage\Users\User $User
     * @return array
     */
    
    public function getUnreadForumThreadsForUser(User $User, $offset = 0, $items_per_page = false) {
        $forums = $this->getAllowedForumsIDsForUser($User, "auth_read");
        
        if ($items_per_page === false) {
            $items_per_page = $User->items_per_page;
        }
        
        /**
         * Get read forums/threads from Memcached
         */
        
        try {
            $tracking_topics = self::getReadItemsForUser($User);
            $tracking_forums = self::getReadItemsForUser($User, "f");
            
            #printArray($tracking_topics);
            #printArray($tracking_forums);
        } catch (Exception $e) {
            // Throw it away
        }
        
        $query = "
            SELECT 
                SQL_CALC_FOUND_ROWS
                plast.post_id, plast.post_time, plast.poster_id AS user_id, u.username, 
                t.topic_id, t.topic_title, t.url_slug, t.forum_id, f.forum_name
            FROM 
                nuke_bbposts AS plast
                LEFT JOIN nuke_bbtopics AS t ON plast.post_id = t.topic_last_post_id
                LEFT JOIN nuke_bbforums AS f ON t.forum_id = f.forum_id
                LEFT JOIN nuke_users AS u ON u.user_id = plast.poster_id
            WHERE 
                plast.post_time >= ?
                AND t.forum_id IN (" . implode(', ', $forums) . ")
            ORDER BY plast.post_time DESC
            LIMIT ?, ?";
        
        $params = array($User->lastvisit, $offset, $items_per_page); 
        
        $posts = $this->db->fetchAll($query, $params); 
        return array(
            "offset" => $offset,
            "items_per_page" => $items_per_page,
            "total" => $this->db->fetchOne("SELECT FOUND_ROWS() AS total"),
            "threads" => $posts
        );
        
        #printArray($posts);die;
        
        return $posts;
        
    }

    /** 
     * Unserialise an array. Extracted from functions.php, phpBB 2.0 stuff
     * @since Version 3.9.1
     * @param string $str
     * @return array
     */
    
    public static function unserializeArray($str) {
        $array = array();
        $list = explode('|', $str);
        
        for ($i = 0, $c = count($list); $i < $c; $i++) {
            $row = explode('=', $list[$i], 2);
            
            if (count($row) == 2) {
                $array[$row[0]] = $row[1];
            }
        }
        
        return $array;
    }
    
    /** 
     * Serialise an array. Extracted from functions.php, phpBB 2.0 stuff
     * @since Version 3.9.1
     * @param array $array
     * @return string
     */
    
    public static function serialize_array($array) {
        if (!is_array($array)) {
            return '';
        }
        
        $str = '';
        
        foreach($array as $var => $value) {
            if ($str) {
                $str .= '|';
            }
            $str .= $var . '=' . str_replace('|', '', $value);
        }
        return $str;
    }
    
    /**
     * Get read threads/forums for a given user
     * @since Version 3.9.1
     * @param \Railpage\Users\User $User
     * @return array
     */
    
    public static function getReadItemsForUser(User $User, $type = "t") {
        
        /**
         * Not logged in - no threads/forums
         */
        
        if ($User->id == 0) {
            return array(); 
        }
        
        /**
         * Find the base name of this cookie/memcached object
         */
        
        $cookiename = sprintf("%s_%s", "phpbb2mysqlrp2", $type);
        
        /**
         * Try and get it from Memcached
         */
        
        try {
            #$key = sprintf("%s:%d", $cookiename, $User->id);
            $key = sprintf("railpage:forums.read;user=%d;type=%s", $User->id, $type);
            
            $Redis = AppCore::getRedis(); 
            $Memcached = AppCore::getMemcached(); 
            
            $result = $Memcached->fetch($key);
            
            if ($result) {
                if (!is_array($result)) {
                    $unserial = self::unserializeArray($result);
                    
                    if (is_array($unserial)) {
                        $result = $unserial;
                        unset($unserial);
                    }
                }
                
                if (!is_array($result)) {
                    $json = json_decode($result, true); 
                    
                    if (is_array($json)) {
                        $result = $json;
                        unset($json);
                    }
                }
                
                if (!is_array($result)) {
                    return array(); 
                }
                
                return $result;
            }
        } catch (Exception $e) {
            // throw it away
        }
        
        /**
         * Fall back to cookies
         */
        
        if (!is_null(filter_input(INPUT_COOKIE, $cookiename))) { #isset($_COOKIE[$cookiename])) {
            $cookiedata = filter_input(INPUT_COOKIE, $cookiename); #$_COOKIE[$cookiename];
            $data = self::unserializeArray($cookiedata);
            
            if (count($data)) {
                self::saveReadItemsForUser($User, $data, $type);
                    
                return $data;
            }
        }
        
        /**
         * Couldn't find shit
         */
        
        if ($type == "f_all") {
            return false;
        }
        
        return array(); 
    }
    
    /**
     * Save read threads/forums for a given user
     * @since Version 3.9.1
     * @param \Railpage\Users\User $User
     * @param array $items
     * @param string $type
     */
    
    public static function saveReadItemsForUser(User $User, $items, $type = "t") {
        
        $cookiename = sprintf("%s_%s", "phpbb2mysqlrp2", $type);
        
        /**
         * Try and get it from Memcached
         */
        
        try {
            #$key = sprintf("%s:%d", $cookiename, $User->id);
            $key = sprintf("railpage:forums.read;user=%d;type=%s", $User->id, $type);
            
            $Redis = AppCore::getRedis(true); 
            $Memcached = AppCore::getMemcached(true);
            $Memcached->save($key, $items, 0);
        } catch (Exception $e) {
            // Throw it away
        }
        
        /**
         * Save it in a cookie just for good luck
         */
        
        $save = is_array($items) ? self::serialize_array($items) : $items;
        
        setcookie($cookiename, $save, strtotime("+1 year"), RP_AUTOLOGIN_PATH, RP_AUTOLOGIN_DOMAIN, RP_SSL_ENABLED, true); 
    }
}