railpage/railpagecore

View on GitHub
lib/Forums/Post.php

Summary

Maintainability
F
5 days
Test Coverage
<?php

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

use Railpage\Users\User;
use Railpage\Users\Factory as UserFactory;
use Railpage\Module;
use Railpage\Url;
use Railpage\AppCore;
use Railpage\Registry;
use Railpage\Debug;
use Exception;
use DateTime;
use ArrayObject;
use stdClass;
use Memcached;
use Doctrine\Common\Cache\MemcachedCache;

/**
 * phpBB post class
 * @since Version 3.0.1
 * @version 3.0.1
 * @author James Morgan
 */

class Post extends Forums {
    
    /**
     * Constant: Cache key for edit lookups
     * @since Version 3.11.0
     * @var const CACHEKEY_EDITS
     */
    
    const CACHEKEY_EDITS = "railpage:forums.post=%d;edits";
    
    /**
     * Post ID
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $id
     */
    
    public $id;
    
    /**
     * Post author user id
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $uid
     */
    
    public $uid;
    
    /**
     * Post timestamp
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $timestamp
     */
    
    public $timestamp;
    
    /**
     * DateTime object of post date
     * @since Version 3.8.7
     * @var \DateTime $Date
     */
    
    public $Date;
    
    /**
     * Post IP
     * @since Version 3.0.1
     * @version 3.0.1
     * @var string $ip
     */
    
    public $ip;
    
    /**
     * Post BBCode enabled
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $flag_bbCode
     */
    
    public $flag_bbCode = 1;
    
    /**
     * Post HTML enabled
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $flag_html
     */
    
    public $flag_html = 0;
    
    /**
     * Post smilies enabled
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $flag_smilies
     */
    
    public $flag_smilies = 1;
    
    /**
     * Post signature enabled
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $flag_signature
     */
    
    public $flag_signature = 1;
    
    /**
     * Post last edit timestamp
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $edit_timestamp
     */
    
    public $edit_timestamp;
    
    /**
     * Post edit count
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $edit_count
     */
    
    public $edit_count = 0;
    
    /**
     * Post reported status
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $reported
     */
    
    public $reported = 0;
    
    /**
     * Post herring count
     * @since Version 3.0.1
     * @version 3.0.1
     * @var int $herring_count
     */
    
    public $herring_count = 0;
    
    /**
     * Post subject
     * @since Version 3.0.1
     * @version 3.0.1
     * @var string $subject
     */
    
    public $subject;
    
    /**
     * Post text
     * @since Version 3.0.1
     * @version 3.0.1
     * @var string $text
     */
    
    public $text;
    
    /**
     * Post rating
     * @since Version 3.2
     * @var int $rating
     */
    
    public $rating = 0;
    
    /** 
     * Latitude
     * @since Version 3.2
     * @var string $lat
     */
     
    public $lat;
    
    /**
     * Longitude
     * @since Version 3.2
     * @var string $lon
     */
    
    public $lon;
    
    /**
     * BBCode UID
     * @since Version 3.2
     * @var string $bbcode_uid
     */
    
    public $bbcode_uid;
    
    /**
     * Thread object
     * @since Version 3.0.1
     * @version 3.0.1
     * @var object $thread
     */
    
    public $thread;
    
    /**
     * Editor version
     * @since Version 3.5
     * @version 3.5
     * @var int $editor_version
     */
    
    public $editor_version;
    
    /**
     * Post URL slug
     * @since Version 3.8.7
     * @var string $url_slug
     */
    
    public $url_slug;
    
    /**
     * Is this post pinned in the thread? (Use for flagging a post as important
     * @since Version 3.9.1
     * @var boolean $pinned
     */
    
    public $pinned = false;
    
    /**
     * Author of this post
     * @since Version 3.8.7
     * @param \Railpage\Users\User $Author
     */
     
    public $Author;
    
    /**
     * Constructor
     * @since Version 3.0.1
     * @version 3.0.1
     * @param object $database
     * @param int $postid
     */
    
    public function __construct($postid = false) {
        $post_timer_start = Debug::GetTimer(); 
        
        parent::__construct();
        
        $this->Module = new Module("forums");
        
        $this->timestamp = time();
        $this->mckey = sprintf("railpage:forums;post=%d", $postid);
        
        $this->Memcached = AppCore::getMemcached();  
        
        if (!$row = $this->Memcached->fetch($this->mckey)) {
            Debug::LogEvent("Could not find forum post in Redis using cache key " . $this->mckey); 
            
            if (filter_var($postid, FILTER_VALIDATE_INT)) {
                $timer = Debug::GetTimer(); 
                
                $query = "SELECT p.*, t.*, u.username, u.user_avatar FROM nuke_bbposts p, nuke_bbposts_text t, nuke_users AS u WHERE u.user_id = p.poster_id AND p.post_id = ? AND t.post_id = p.post_id LIMIT 1";
                
                $row = $this->db->fetchRow($query, $postid);
                $rs = $this->Memcached->save($this->mckey, $row, 43200);
                
                Debug::LogEvent("Fetch forum post from database", $timer); 
                
                if (!$rs) {
                    Debug::LogEvent("!! Failed to store forum post in cache provider"); 
                }
            } elseif (is_string($postid)) {
                $query = "SELECT p.*, t.*, u.username, u.user_avatar FROM nuke_bbposts p, nuke_bbposts_text t, nuke_users AS u WHERE u.user_id = p.poster_id AND t.url_slug = ? AND t.post_id = p.post_id LIMIT 1";
                
                $row = $this->db->fetchRow($query, $postid);
                $rs = $this->Memcached->save($this->mckey, $row, 43200);
            }
        }
            
        if (isset($row) && is_array($row)) {
            $this->id = $row['post_id'];
            $this->thread = ForumsFactory::CreateThread($row['topic_id']);
            $this->uid = $row['poster_id'];
            $this->username = $row['username'];
            $this->user_avatar = $row['user_avatar'];
            $this->timestamp = $row['post_time'];
            $this->ip = $row['poster_ip'];
            $this->flag_bbCode = $row['enable_bbcode'];
            $this->flag_html = $row['enable_html'];
            $this->flag_smilies = $row['enable_smilies'];
            $this->flag_signature = $row['enable_sig'];
            $this->edit_timestamp = $row['post_edit_time'];
            $this->edit_count = $row['post_edit_count'];
            $this->reported = $row['post_reported'];
            $this->herring_count = $row['post_edit_count'];
            $this->bbcodeuid = $row['bbcode_uid'];
            $this->subject = $row['post_subject'];
            $this->text = stripslashes($row['post_text']);
            $this->old_text = stripslashes($row['post_text']);
            $this->rating = $row['post_rating'];
            $this->bbcode_uid = $row['bbcode_uid'];
            $this->editor_version = $row['editor_version']; 
            $this->url_slug = $row['url_slug'];
            $this->pinned = isset($row['pinned']) ? (bool) $row['pinned'] : false;
            
            if (empty($this->url_slug)) {
                $this->createSlug();
                $this->commit();
            }
            
            $this->lat = $row['lat']; 
            $this->lon = $row['lon'];
            
            $this->Date = new DateTime;
            $this->Date->setTimestamp($row['post_time']);
            $this->Author = UserFactory::CreateUser($row['poster_id']);
            
            $this->makeLinks(); 
        }
        
        Debug::LogEvent(__METHOD__, $post_timer_start); 
    }
    
    /**
     * Post validator
     *
     * Checks that the post is OK before committing it to the database
     * @since Version 3.0.1
     * @version 3.0.1
     * @return boolean
     */
    
    public function validate() {
        
        if (is_null($this->bbcode_uid)) {
            $this->bbcode_uid = "sausages";
        }
        
        if (is_null($this->lat)) {
            $this->lat = 0;
        }
        
        if (is_null($this->lon)) {
            $this->lon = 0;
        }
        
        if (is_null($this->edit_timestamp)) {
            $this->edit_timestamp = 0;
        }
        
        if (is_null($this->edit_count)) {
            $this->edit_count = 0;
        }
        
        if (empty($this->uid) && $this->Author instanceof User) {
            $this->uid = $this->Author->id;
        }
        
        if (empty($this->uid)) {
            throw new Exception("No post author specified");
        }
        
        if (empty($this->ip)) {
            $this->ip = filter_input(INPUT_SERVER, "REMOTE_ADDR", FILTER_SANITIZE_URL); #$_SERVER['REMOTE_ADDR'];
        }
        
        if ($this->ip == null) {
            $this->ip = "";
        }
        
        if (empty($this->url_slug)) {
            $this->createSlug();
        }
        
        if (empty($this->url_slug)) {
            $this->url_slug = "";
        }
        
        if (!filter_var($this->timestamp)) {
            $this->timestamp = time(); 
        }
        
        if (empty($this->text)) {
            throw new Exception("No post text was submitted"); 
        }
        
        
        return true;
    }
    
    /**
     * Commit this post to the database
     *
     * If $this->id is not specified, it will try to create a new post
     * @since Version 3.0.1
     * @version 3.3
     * @return boolean
     */
    
    public function commit() {
        if (empty($this->bbcode_uid)) {
            $this->bbcode_uid = crc32($this->text);
        }
        
        // Set the editor version
        $this->editor_version = defined("EDITOR_VERSION") ? EDITOR_VERSION : 2; 
        
        /** 
         * Validate the post
         */
        
        $this->validate(); 
        
        /** 
         * Start the timer if we're in debug mode
         */
        
        if (RP_DEBUG) {
            global $site_debug;
            $debug_timer_start = microtime(true);
        }
        
        /**
         * Process @mentions
         */
         
        if (function_exists("process_mentions")) {
            $this->text = process_mentions($this->text);
        }
        
        /**
         * Update this information in Redis
         */
        
        $this->mckey = sprintf("railpage:forums;post=%d", $this->id);
        $this->Memcached->delete(sprintf(self::CACHEKEY_EDITS, $this->id));
        $this->Redis->delete(sprintf(self::CACHEKEY_EDITS, $this->id));
        $this->Memcached->delete($this->mckey); 
        $this->Redis->delete($this->mckey);
        $this->Redis->delete(sprintf("railpage:forums.post=%d;processed_message", $this->id));
        
        /**
         * If this is an existing post, insert it into the edit table before we proceed
         */
        
        if (filter_var($this->id, FILTER_VALIDATE_INT)) {
            global $User;
            
            #$CurrentPost = new Post($this->id);
            
            if ($this->old_text != $this->text) {
                $dataArray = array(); 
                $dataArray['post_id']       = $this->id;
                $dataArray['thread_id']     = $this->thread->id;
                $dataArray['poster_id']     = $this->uid;
                $dataArray['edit_time']     = time(); 
                $dataArray['edit_body']     = $this->old_text;
                $dataArray['bbcode_uid']    = $this->bbcode_uid;
                $dataArray['editor_id']     = $User->id;
                
                if ($this->db->insert("nuke_bbposts_edit", $dataArray)) {
                    $changes = array(
                        "Forum" => $this->thread->forum->name,
                        "Forum ID" => $this->thread->forum->id,
                        "Thread" => $this->thread->title,
                        "Thread ID" => $this->thread->id,
                        "Author user ID" => $this->uid
                    );
                    
                    try {
                        $Event = new \Railpage\SiteEvent; 
                        $Event->user_id = $User->id; 
                        $Event->title = "Forum post edited";
                        $Event->module_name = "forums";
                        $Event->args = $changes; 
                        $Event->key = "post_id";
                        $Event->value = $this->id;
                        
                        $Event->commit();
                    } catch (Exception $e) {
                        $Error->save($e); 
                    }
                }
            }
        }
        
        unset($CurrentPost); unset($dataArray); unset($query);
        
        /**
         * Insert or update the post
         */
        
        $data = array(
            "topic_id" => $this->thread->id,
            "forum_id" => $this->thread->forum->id,
            "poster_id" => $this->uid,
            "post_time" => $this->timestamp,
            "poster_ip" => $this->ip,
            "enable_bbcode" => $this->flag_bbCode,
            "enable_html" => $this->flag_html,
            "enable_smilies" => $this->flag_smilies,
            "enable_sig" => $this->flag_signature,
            "post_rating" => $this->rating,
            "post_reported" => $this->reported,
            "post_herring_count" => $this->herring_count,
            "lat" => $this->lat,
            "lon" => $this->lon,
            "pinned" => intval($this->pinned)
        );
        
        $text = array(
            "bbcode_uid" => $this->bbcode_uid,
            "post_subject" => $this->subject,
            "post_text" => $this->text,
            "editor_version" => $this->editor_version,
            "url_slug" => $this->url_slug
        );
        
        if (filter_var($this->id, FILTER_VALIDATE_INT)) {
            $data['post_edit_count'] = $this->edit_count++;
            
            $where = array(
                "post_id = ?" => $this->id
            );
            
            $this->db->update("nuke_bbposts", $data, $where);
            $this->db->update("nuke_bbposts_text", $text, $where);
            $verb = "Update";
        } else {
            $this->db->insert("nuke_bbposts", $data); 
            $this->id = $this->db->lastInsertId(); 
            
            $text['post_id'] = $this->id;
            $this->db->insert("nuke_bbposts_text", $text);
            $verb = "Insert";
            
            $this->thread->reDrawStats();
        }
            
        if (RP_DEBUG) {
            $site_debug[] = "Zend_DB: " . $verb . " Forum post ID " . $this->id . " in " . round(microtime(true) - $debug_timer_start, 5) . "s";
        }
        
        $this->makeLinks();
        
        if (!$this->Author instanceof User) {
            $this->loadAuthor();
        }
        
        return true;
    }
    
    /**
     * Load the author
     * @since Version 3.8.7
     * @return $this
     */
    
    public function loadAuthor() {
        $this->Author = UserFactory::CreateUser($this->uid);
        
        return $this;
    }
    
    /**
     * Delete this post into Thread Storage
     * @since Version 3.8.7
     * @param \Railpage\Users\User $Staff Required for logging of deletion
     * @return $this
     */
    
    public function delete(User $Staff) {
        if ($this->thread->id == "9448") {
            return $this;
        }
        
        $string = sprintf("From topic [url=%s]%s[/url] (#%d), forum [url=%s]%s[/url] (#%d), post #%d, deleted by [url=%s]%s[/url] (uid %d)\n\n", 
            $this->thread->url, $this->thread->title, $this->thread->id,
            $this->thread->forum->url, $this->thread->forum->name, $this->thread->forum->id,
            $this->id,
            $Staff->url, $Staff->username, $Staff->id
        );
        
        $this->text = $string.$this->text;
        
        $OldThread = $this->thread;
        
        $this->thread = new Thread("9448");
        
        if ($this->commit()) {
            $OldThread->reDrawStats();
            
            if (!$this->Author instanceof User) {
                $this->loadAuthor();
                $this->Author->chaff(20);
            }
        }
        
        return $this;
    }
    
    /**
     * Find edits of this post 
     * @since Version 3.8.7
     * @return \ArrayObject
     */
    
    public function getEdits() {
        
        $cacheKey = sprintf(self::CACHEKEY_EDITS, $this->id); 
        
        if (!$edits = AppCore::getMemcached()->fetch($cacheKey)) {
            $query = "SELECT editor_id, edit_time, edit_body, bbcode_uid FROM nuke_bbposts_edit WHERE post_id = ? ORDER BY edit_time DESC";
            $edits = $this->db->fetchAll($query, $this->id);
            AppCore::getMemcached()->save($cacheKey, $edits, 8600 * 7);
        }
            
        $return = array(); 
        
        foreach ($edits as $row) {
            $DateTime = new DateTime;
            $DateTime->setTimestamp($row['edit_time']);
            $return = new stdClass;
            
            $return->uid = $row['editor_id'];
            $return->text = $row['edit_body'];
            $return->Date = $DateTime;
            $return->bbcode_udi = $row['bbcode_uid'];
            
            yield $return;
        }
        
        return;
    }
    
    /**
     * Get the number of edits of this post
     * @since Version 3.8.7
     * @return int
     */
    
    public function getNumEdits() {
        
        $cacheKey = sprintf(self::CACHEKEY_EDITS, $this->id); 
        
        if ($edits = AppCore::getMemcached()->fetch($cacheKey)) {
            return count($edits); 
        }
        
        $query = "SELECT editor_id, edit_time, edit_body, bbcode_uid FROM nuke_bbposts_edit WHERE post_id = ? ORDER BY edit_time DESC";
        
        $result = $this->db->fetchAll($query, $this->id);
        
        if (is_array($result)) {
            return count($result);
        }
        
        return 0;
        
    }
    
    /**
     * Set the author of this post
     * @since Version 3.8.7
     * @param \Railpage\Users\User $User
     * @return $this
     */
    
    public function setAuthor(User $User) {
        if (!$this->Author instanceof User) {
            $this->Author = $User;
        }
        
        return $this;
    }
    
    /**
     * Set the thread that this post belongs in
     * @since Version 3.8.7
     * @param \Railpage\Forums\Thread $Thread
     * @return $this
     */
    
    public function setThread(Thread $Thread) {
        if (!$this->thread instanceof Thread || !filter_var($this->thread->id, FILTER_VALIDATE_INT)) {
            $this->thread = $Thread;
        }
        
        return $this;
    }
    
    /**
     * Get the thread that this post belomgs in
     * @since Version 3.8.7
     * @return \Railpage\Forums\Thread
     */
    
    public function getThread() {
        if (!$this->thread instanceof Thread) {
            throw new Exception("Cannot find the thread that this post belongs to");
        }
        
        return $this->thread;
    }
    
    /**
     * Create a URL slug
     * @since Version 3.8.7
     */
    
    private function createSlug() {
        
        $proposal = !empty($this->subject) ? substr(create_slug($this->subject), 0, 60) : $this->id;
        
        $result = $this->db->fetchAll("SELECT post_id FROM nuke_bbposts_text WHERE url_slug = ?", $proposal); 
        
        if (count($result)) {
            $proposal .= count($result);
        }
        
        $this->url_slug = $proposal;
        
    }
    
    /**
     * Make links to this post
     * @since Version 3.8.7
     * @return \Railpage\Forums\Post
     */
    
    public function makeLinks() {
        $this->url = new Url(sprintf("/f-p%d.htm#%d", $this->id, $this->id));
        $this->url->single = sprintf("/forums?mode=post.single&id=%d", $this->id);
        $this->url->report = sprintf("/f-report-%d.htm", $this->id);
        $this->url->reply = sprintf("/f-po-quote-%d.htm", $this->id);
        $this->url->delete = sprintf("/f-po-delete-%d.htm", $this->id);
        $this->url->edit = sprintf("/f-po-editpost-%d.htm", $this->id);
        $this->url->replypm = sprintf("/messages/new/from/%d/", $this->id);
        $this->url->iplookup = sprintf("/moderators?mode=ip.lookup&ip=%s", $this->ip);
        $this->url->herring = sprintf("/f-herring-p-%d.htm", $this->id);
        
        if ($this->thread->forum->id == 71) {
            $this->url->developers = sprintf("/%s/d/%s/%s", "developers", $this->thread->url_slug, $this->url_slug);
        }
        
        return $this;
    }
    
    /**
     * Add a reputation marker to this post
     * @since Version 3.9
     * @param int $reputation_type_id
     * @return int
     */
    
    public function addReputationMarker($reputation_type_id = false) {
        if (!filter_var($reputation_type_id, FILTER_VALIDATE_INT)) {
            throw new Exception("Cannot add a reputation marker to this post because the supplied type ID (\$reputation_type_id) is not valid");
        }
        
        if (!$this->User instanceof User) {
            throw new Exception("Cannot add a reputation marker to this post because no valid user object has been specified");
        }
        
        /**
         * Check if this user has already rated this post
         */
        
        $query = "SELECT id FROM nuke_bbposts_reputation WHERE post_id = ? AND user_id = ?";
        
        $id = $this->db->fetchOne($query, array($this->id, $this->User->id));
        
        /**
         * Add the rating
         */
        
        $data = array(
            "post_id" => $this->id,
            "type" => $reputation_type_id,
            "user_id" => $this->User->id
        );
        
        $markers = $this->getReputationMarkers(); 
        
        foreach ($markers as $marker) {
            if ($marker['id'] == $reputation_type_id) {
                $reputation_type_name = $marker['name'];
                break;
            }
        }
        
        if (filter_var($id, FILTER_VALIDATE_INT)) {
            $where = array(
                "id = ?" => $id
            );
            
            $this->db->update("nuke_bbposts_reputation", $data, $where); 
        } else {
            $this->db->insert("nuke_bbposts_reputation", $data); 
            $id = $this->db->lastInsertId(); 
        }
        
        /** 
         * Add a note to this user
         */
        
        try {
            $this->User->addNote(sprintf("Rated <a href='%s'>Post ID %d</a> as %s", $this->url->url, $this->id, $reputation_type_name));
        } catch (Exception $e) {
            // Throw it away
        }
        
        $markers = $this->getReputationMarkers(true); 
        
        return $id;
    }
    
    /**
     * Get reputation markers for this post
     * @since Version 3.9
     * @return array
     * @param boolean $force Force an update, removing whatever is cached in Memcache
     */
    
    public function getReputationMarkers($force = false) {
        $mckey = sprintf("railpage:forums.post;id=%d;getreputationmarkers", $this->id);
        
        $Registry = Registry::getInstance();
        
        $Memcached = new Memcached;
        $Memcached->addServer($Config->Memcached->Host, 11211);
        
        #$cacheDriver = new MemcachedCache;
        #$cacheDriver->setMemcached($Memcached);
        
        #$this->Memcached = $cacheDriver;
        
        if ($force || !$types = $Memcached->get($mckey)) {
            $query = "SELECT r.*, u.username FROM nuke_bbposts_reputation AS r LEFT JOIN nuke_users AS u ON r.user_id = u.user_id WHERE r.post_id = ?";
            
            $types = $this->getReputationTypes(); 
            
            foreach ($this->db->fetchAll($query, $this->id) as $row) {
                $types[$row['type']]['votes'][] = $row;
                $types[$row['type']]['count'] = count($types[$row['type']]['votes']);
            }
            
            $Memcached->set($mckey, $types, 0);
        }
        
        return $types;
    }
}