Seditio Source
Root |
./othercms/ips_4.3.4/applications/forums/modules/front/forums/topic.php
<?php
/**
 * @brief        Topic View
 * @author        <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright    (c) Invision Power Services, Inc.
 * @license        https://www.invisioncommunity.com/legal/standards/
 * @package        Invision Community
 * @subpackage    Forums
 * @since        08 Jan 2014
 */

namespace IPS\forums\modules\front\forums;

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
   
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
    exit;
}

/**
 * Topic View
 */
class _topic extends \IPS\Content\Controller
{
   
/**
     * [Content\Controller]    Class
     */
   
protected static $contentModel = 'IPS\forums\Topic';
   
   
/**
     * Execute
     *
     * @return    void
     */
   
public function execute()
    {
        \
IPS\Output::i()->jsFiles    = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js('front_topic.js', 'forums' ) );
       
parent::execute();
    }

   
/**
     * View Topic
     *
     * @return    void
     */
   
protected function manage()
    {
       
/* Load topic */
       
$topic = parent::manage();
        \
IPS\Member::loggedIn()->language()->words['submit_comment'] = \IPS\Member::loggedIn()->language()->addToStack( 'submit_reply', FALSE );
       
       
/* If there's only one forum we actually don't want the nav */
       
if ( \IPS\forums\Forum::theOnlyForum() or \IPS\forums\Forum::isSimpleView() )
        {
           
$topicBreadcrumb = array_pop( \IPS\Output::i()->breadcrumb );
            \
IPS\Output::i()->breadcrumb = isset( \IPS\Output::i()->breadcrumb['module'] ) ? array( 'module' => \IPS\Output::i()->breadcrumb['module'] ) : array();
            \
IPS\Output::i()->breadcrumb[] = $topicBreadcrumb;
        }

       
/* If it failed, it might be because we want a password */
       
if ( $topic === NULL )
        {
           
$forum = NULL;
            try
            {
               
$topic = \IPS\forums\Topic::load( \IPS\Request::i()->id );
               
$forum = $topic->container();
                if (
$forum->can('view') and !$forum->loggedInMemberHasPasswordAccess() )
                {
                    \
IPS\Output::i()->redirect( $forum->url()->setQueryString( 'topic', \IPS\Request::i()->id ) );
                }
               
                if ( !
$topic->canView() )
                {
                    if (
$topic instanceof \IPS\Content\Hideable and $topic->hidden() )
                    {
                       
/* If the item is hidden we don't want to show the custom no permission error as the conditions may not apply */
                       
\IPS\Output::i()->error( 'node_error', '2F173/O', 404, '' );
                    }
                    else
                    {
                        \
IPS\Output::i()->error(  $forum ? $forum->errorMessage() : 'node_error_no_perm', '2F173/H', 403, '' );
                    }
                }
            }
            catch ( \
OutOfRangeException $e )
            {
               
/* Nope, just a generic no access error */
               
\IPS\Output::i()->error( 'node_error', '2F173/1', 404, '' );
            }
        }
       
       
/* Legacy findpost redirect */
       
if ( \IPS\Request::i()->findpost )
        {
            \
IPS\Output::i()->redirect( $topic->url()->setQueryString( array( 'do' => 'findComment', 'comment' => \IPS\Request::i()->findpost ) ), NULL, 301 );
        }
        elseif ( \
IPS\Request::i()->p )
        {
            \
IPS\Output::i()->redirect( $topic->url()->setQueryString( array( 'do' => 'findComment', 'comment' => \IPS\Request::i()->p ) ), NULL, 301 );
        }
        elseif ( \
IPS\Request::i()->pid )
        {
            \
IPS\Output::i()->redirect( $topic->url()->setQueryString( array( 'do' => 'findComment', 'comment' => \IPS\Request::i()->pid ) ), NULL, 301 );
        }
       
        if ( \
IPS\Request::i()->view )
        {
           
$this->_doViewCheck();
        }

       
/* If this is an AJAX request fetch the comment form now. The HTML will be cached so calling here and then again in the template has no overhead
            and this is necessary if you entered into a topic with &queued_posts=1, approve the posts, then try to reply. Otherwise, clicking into the
            editor produces an error when the getUploader=1 call occurs, and submitting a reply results in an error. */
       
if ( \IPS\Request::i()->isAjax() and ( !isset( \IPS\Request::i()->preview ) OR !\IPS\Request::i()->preview ) )
        {
           
$topic->commentForm();
        }
   
       
/* AJAX hover preview? */
       
if ( \IPS\Request::i()->isAjax() and \IPS\Request::i()->preview )
        {
           
$postClass = '\IPS\forums\Topic\Post';

            if(
$topic->isArchived() )
            {
               
$postClass = '\IPS\forums\Topic\ArchivedPost';
            }

           
$firstPost = $postClass::load( $topic->topic_firstpost );
           
           
$topicOverview = array( 'firstPost' => array( $topic->isQuestion() ? 'question_mainTab' : 'first_post', $firstPost ) );

            if (
$topic->posts > 1 )
            {
               
$latestPost = $topic->comments( 1, 0, 'date', 'DESC' );
               
$topicOverview['latestPost'] = array( $topic->isQuestion() ? 'latest_answer' : 'latest_post', $latestPost );
           
               
$timeLastRead = $topic->timeLastRead();
                if (
$timeLastRead instanceof \IPS\DateTime AND $topic->unread() !== 0 )
                {
                   
$firstUnread = $topic->comments( 1, NULL, 'date', 'asc', NULL, NULL, $timeLastRead );
                    if(
$firstUnread instanceof \IPS\forums\Topic\Post AND $firstUnread->date !== $latestPost->date AND $firstUnread->date !== $firstPost->date )
                    {
                       
$topicOverview['firstUnread'] = array( 'first_unread_post_hover', $topic->comments( 1, NULL, 'date', 'asc', NULL, NULL, $timeLastRead ) );
                    }
                }            
            }

            if (
$topic->isQuestion() and $topic->topic_answered_pid )
            {
               
$topicOverview['bestAnswer'] = array( 'best_answer_post', \IPS\forums\Topic\Post::load( $topic->topic_answered_pid ) );
            }

            \
IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'forums' )->topicHover( $topic, $topicOverview ) );
            return;
        }
       
       
$topic->container()->setTheme();
       
       
/* Watch for votes */
       
if ( $poll = $topic->getPoll() )
        {
           
$poll->attach( $topic );
        }
               
       
/* How are we sorting posts? */
       
$question = NULL;
       
$offset = NULL;
       
$order = 'date';
       
$orderDirection = 'asc';
       
$where = NULL;
        if( \
IPS\forums\Topic::modPermission( 'unhide', NULL, $topic->container() ) AND \IPS\Request::i()->queued_posts )
        {
            if (
$topic->isArchived() )
            {
               
$where = 'archive_queued=1';
            }
            else
            {
               
$where = 'queued=1';
            }
           
           
$queuedPagesCount = ceil( \IPS\Db::i()->select( 'COUNT(*)', 'forums_posts', array( 'topic_id=? AND queued=1', $topic->id ) )->first() / $topic->getCommentsPerPage() );
           
$pagination = ( $queuedPagesCount > 1 ) ? $topic->commentPagination( array( 'queued_posts', 'sortby' ), 'pagination', $queuedPagesCount ) : NULL;
           
            if (
$topic->isQuestion() )
            {
               
$question = $topic->comments( 1, 0 );
            }
        }
        else
        {
            if (
$topic->isQuestion() )
            {
               
$question    = $topic->comments( 1, 0 );
                try
                {
                   
$question->warning = \IPS\core\Warnings\Warning::constructFromData( \IPS\Db::i()->select( '*', 'core_members_warn_logs', array( array( 'wl_content_app=? AND wl_content_module=? AND wl_content_id1=? AND wl_content_id2=?', 'forums', 'forums-comment', $question->topic_id, $question->pid ) ) )->first() );
                }
                catch ( \
UnderflowException $e ) { }
                       
               
$page        = ( isset( \IPS\Request::i()->page ) ) ? intval( \IPS\Request::i()->page ) : 1;

                if(
$page < 1 )
                {
                   
$page    = 1;
                }

               
$offset        = ( ( $page - 1 ) * $topic::getCommentsPerPage() ) + 1;
               
                if ( ( !isset( \
IPS\Request::i()->sortby ) or \IPS\Request::i()->sortby != 'date' ) )
                {
                    if (
$topic->isArchived() )
                    {
                       
$order = "archive_is_first desc, archive_bwoptions";
                       
$orderDirection = 'desc';
                    }
                    else
                    {
                       
$order = "new_topic DESC, post_bwoptions DESC, post_field_int DESC, post_date";
                       
$orderDirection = 'ASC';
                    }
                }
            }
           
$pagination = ( $topic->commentPageCount() > 1 ) ? $topic->commentPagination( array( 'sortby' ) ) : NULL;
        }    
       
$comments = $topic->comments( NULL, $offset, $order, $orderDirection, NULL, NULL, NULL, $where, FALSE, ( isset( \IPS\Request::i()->showDeleted ) ) );
       
$current  = current( $comments );
       
reset( $comments );

        if( !
count( $comments ) AND !$topic->isQuestion() )
        {
            \
IPS\Output::i()->error( 'no_posts_returned', '2F173/L', 404, '' );
        }

       
/* On pages 2 and more, it will show the first post on that page, and that is fine as the title has - Page 2 in it meaning its a different page */
       
$metaDescription = $topic->metaDescription( isset( $_SESSION['_findComment'] ) ? NULL : ( $topic->isQuestion() ? $question->content() : $current->content() ) );

        \
IPS\Output::i()->metaTags['description'] = \IPS\Output::i()->metaTags['og:description'] = $metaDescription;

       
/* Mark read */
       
if( !$topic->isLastPage() )
        {
           
$maxTime    = 0;

            foreach(
$comments as $comment )
            {
               
$maxTime    = ( $comment->mapped('date') > $maxTime ) ? $comment->mapped('date') : $maxTime;
            }

           
$topic->markRead( NULL, $maxTime );
        }

       
$votes        = array();
       
$topicVotes = array();

       
/* Get post ratings for this user */
       
if ( $topic->isQuestion() && \IPS\Member::loggedIn()->member_id )
        {
           
$votes        = $topic->answerVotes( \IPS\Member::loggedIn() );
           
$topicVotes    = $topic->votes();
        }
       
        if (
$topic->isQuestion() )
        {
            \
IPS\Member::loggedIn()->language()->words[ 'topic__comment_placeholder' ] = \IPS\Member::loggedIn()->language()->addToStack( 'question__comment_placeholder', FALSE );
        }
       
       
/* Online User Location */
       
\IPS\Session::i()->setLocation( $topic->url(), ( $topic->container()->password or !$topic->container()->can_view_others or $topic->container()->min_posts_view ) ? 0 : $topic->onlineListPermissions(), 'loc_forums_viewing_topic', array( $topic->title => FALSE ) );

       
/* Next unread */
       
try
        {
           
$nextUnread    = $topic->nextUnread();
        }
        catch( \
Exception $e )
        {
           
$nextUnread    = NULL;
        }

       
/* Add Json-LD */
       
\IPS\Output::i()->jsonLd['topic']    = array(
           
'@context'        => "http://schema.org",
           
'@type'            => $topic->isQuestion() ? 'Question' : 'DiscussionForumPosting',
           
'url'            => (string) $topic->url(),
           
'discussionUrl'    => (string) $topic->url(),
           
'name'            => $topic->mapped('title'),
           
'headline'        => $topic->mapped('title'),
           
'text'            => strip_tags( $topic->isQuestion() ? $question->truncated() : $current->truncated() ),
           
'dateCreated'    => \IPS\DateTime::ts( $topic->start_date )->format( \IPS\DateTime::ISO8601 ),
           
'datePublished'    => \IPS\DateTime::ts( $topic->start_date )->format( \IPS\DateTime::ISO8601 ),
           
'pageStart'        => 1,
           
'pageEnd'        => $topic->commentPageCount(),
           
/* Image is required, but we don't have "topic images", so we'll use topic starter's profile photo for now */
           
'image'            => (string) \IPS\Http\Url::external( \IPS\Member::load( $topic->starter_id )->get_photo() )->setScheme( \IPS\Request::i()->isSecure() ? 'https' : 'http' ),
           
'author'        => array(
               
'@type'        => 'Person',
               
'name'        => $topic->starter_name ?: \IPS\Member::load( $topic->starter_id )->name,
               
'image'        => \IPS\Member::load( $topic->starter_id )->get_photo()
            ),
           
'interactionStatistic'    => array(
                array(
                   
'@type'                    => 'InteractionCounter',
                   
'interactionType'        => "http://schema.org/ViewAction",
                   
'userInteractionCount'    => $topic->views
               
),
                array(
                   
'@type'                    => 'InteractionCounter',
                   
'interactionType'        => "http://schema.org/CommentAction",
                   
'userInteractionCount'    => $topic->posts
               
),
                array(
                   
'@type'                    => 'InteractionCounter',
                   
'interactionType'        => "http://schema.org/FollowAction",
                   
'userInteractionCount'    => \IPS\forums\Topic::containerFollowerCount( $topic->container() )
                ),
            )
        );

       
/* Are ratings allowed? */
       
if( !$topic->isArchived() AND $topic->container()->forum_allow_rating AND $topic->averageRating() )
        {
            \
IPS\Output::i()->jsonLd['topic']['aggregateRating'] = array(
               
'@type'            => 'AggregateRating',
               
'ratingValue'    => $topic->averageRating(),
               
'ratingCount'    => $topic->numberOfRatings(),
            );
        }

       
/* Do we have a real author */
       
if( $topic->starter_id )
        {
            \
IPS\Output::i()->jsonLd['topic']['author']['url']    = (string) \IPS\Member::load( $topic->starter_id )->url();
        }

        if(
$topic->isQuestion() )
        {
            \
IPS\Output::i()->jsonLd['topic']['answerCount'] = $topic->posts ? $topic->posts - 1 : 0;
            \
IPS\Output::i()->jsonLd['topic']['upvoteCount'] = intval( $topic->question_rating );

            if(
$topic->topic_answered_pid )
            {
                try
                {
                   
$answer = \IPS\forums\Topic\Post::load( $topic->topic_answered_pid );

                    \
IPS\Output::i()->jsonLd['topic']['acceptedAnswer'] = array(
                       
'@type'        => 'Answer',
                       
'text'        => strip_tags( $answer->truncated() ),
                       
'url'        => (string) $answer->url(),
                       
'mainEntityOfPage'    => (string) $topic->url(),
                       
'dateCreated'    => \IPS\DateTime::ts( $answer->post_date )->format( \IPS\DateTime::ISO8601 ),
                       
'upvoteCount'    => $answer->post_field_int,
                       
'author'    => array(
                           
'@type'        => 'Person',
                           
'name'        => $answer->author_name ?: \IPS\Member::load( $answer->author_id )->name,
                           
'image'        => \IPS\Member::load( $answer->author_id )->get_photo()
                        ),
                    );

                    if(
$answer->author_id )
                    {
                        \
IPS\Output::i()->jsonLd['topic']['acceptedAnswer']['author']['url']    = (string) \IPS\Member::load( $answer->author_id )->url();
                    }
                }
                catch( \
OutOfRangeException $e ){}
            }
        }
        else
        {
            \
IPS\Output::i()->jsonLd['topic']['comment'] = array();

           
$i = 0;
            foreach(
$comments as $comment )
            {
                \
IPS\Output::i()->jsonLd['topic']['comment'][ $i ] = array(
                   
'@type'        => 'Comment',
                   
'url'        => (string) $comment->url(),
                   
'author'    => array(
                       
'@type'        => 'Person',
                       
'name'        => $comment->author_name ?: \IPS\Member::load( $comment->author_id )->name,
                       
'image'        => \IPS\Member::load( $comment->author_id )->get_photo()
                    ),
                   
'dateCreated'    => \IPS\DateTime::ts( $comment->post_date )->format( \IPS\DateTime::ISO8601 ),
                   
'text'            => strip_tags( $comment->truncated() ),
                   
'mainEntityOfPage'    => (string) $topic->url(),
                );

                if(
$comment->author_id )
                {
                    \
IPS\Output::i()->jsonLd['topic']['comment'][ $i ]['author']['url']    = (string) \IPS\Member::load( $comment->author_id )->url();
                }

               
$i++;
            }
        }

       
/* Show topic */
       
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'topics' )->topic( $topic, $comments, $question, $votes, $nextUnread, $pagination, $topicVotes );
    }

   
/**
     * Check our view method and act accordingly (redirect if appropriate)
     *
     * @return    void
     */
   
protected function _doViewCheck()
    {
        try
        {
           
$class    = static::$contentModel;
           
$topic    = $class::loadAndCheckPerms( \IPS\Request::i()->id );
           
            switch( \
IPS\Request::i()->view )
            {
                case
'getnewpost':
                    \
IPS\Output::i()->redirect( $topic->url( 'getNewComment' ) );
                break;
               
                case
'getlastpost':
                    \
IPS\Output::i()->redirect( $topic->url( 'getLastComment' ) );
                break;
            }
        }
        catch( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/F', 403, '' );
        }
    }
   
   
/**
     * Edit topic
     *
     * @return    void
     */
   
public function edit()
    {
        try
        {
           
$class = static::$contentModel;
           
$topic = $class::loadAndCheckPerms( \IPS\Request::i()->id );
           
$forum = $topic->container();
           
$forum->setTheme();
        }
        catch ( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'no_module_permission', '2F173/D', 403, 'no_module_permission_guest' );
        }
       
        if (
$forum->forums_bitoptions['bw_enable_answers'] )
        {
            \
IPS\Member::loggedIn()->language()->words['topic_mainTab'] = \IPS\Member::loggedIn()->language()->addToStack( 'question_mainTab', FALSE );
        }
       
       
// We check if the form has been submitted to prevent the user loosing their content
       
if ( isset( \IPS\Request::i()->form_submitted ) )
        {
            if ( !
$topic->couldEdit() )
            {
                \
IPS\Output::i()->error( 'edit_no_perm_err', '2F173/E', 403, '' );
            }
        }
        else
        {
            if ( !
$topic->canEdit() )
            {
                \
IPS\Output::i()->error( 'edit_no_perm_err', '2F173/E', 403, '' );
            }
        }
       
       
$formElements = $class::formElements( $topic, $forum );

       
$hasModOptions = FALSE;
       
       
/* We used to just check against the ability to lock, however this may not be enough - a moderator could pin, for example, but not lock */
       
foreach( array( 'lock', 'pin', 'hide', 'feature' ) AS $perm )
        {
            if (
$class::modPermission( $perm, NULL, $forum ) )
            {
               
$hasModOptions = TRUE;
                break;
            }
        }
       
       
$form = $topic->buildEditForm();
       
        if (
$values = $form->values() )
        {
            if (
$topic->canEdit() )
            {                
               
$topic->processForm( $values );
               
$topic->save();
               
$topic->processAfterEdit( $values );

               
/* Moderator log */
               
\IPS\Session::i()->modLog( 'modlog__item_edit', array( $topic::$title => FALSE, $topic->url()->__toString() => FALSE, $topic::$title => TRUE, $topic->mapped( 'title' ) => FALSE ), $topic );

                \
IPS\Output::i()->redirect( $topic->url() );
            }
            else
            {
               
$form->error = \IPS\Member::loggedIn()->language()->addToStack('edit_no_perm_err');
            }
        }

       
$formTemplate = $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'submit', 'forums' ) ), 'createTopicForm' ), $forum, $hasModOptions, $topic );

       
$title = $forum->forums_bitoptions['bw_enable_answers'] ? 'edit_question' : 'edit_topic';

        \
IPS\Output::i()->sidebar['enabled'] = FALSE;
        \
IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'submit' )->createTopic( $formTemplate, $forum, $title );
        \
IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( $title );
       
        if ( !\
IPS\forums\Forum::theOnlyForum() and ! \IPS\forums\Forum::isSimpleView() )
        {
            try
            {
                foreach(
$forum->parents() AS $parent )
                {
                    \
IPS\Output::i()->breadcrumb[] = array( $parent->url(), $parent->_title );
                }
                \
IPS\Output::i()->breadcrumb[] = array( $forum->url(), $forum->_title );
            }
            catch( \
Exception $e ) {}
        }
       
        \
IPS\Output::i()->breadcrumb[] = array( $topic->url(), $topic->mapped('title') );
        \
IPS\Output::i()->breadcrumb[] = array( NULL, \IPS\Member::loggedIn()->language()->addToStack( $title ) );
    }

   
/**
     * Unarchive
     *
     * @return    void
     */
   
public function unarchive()
    {
        \
IPS\Session::i()->csrfCheck();
       
        try
        {
           
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
            if ( !
$topic->canUnarchive() )
            {
                throw new \
OutOfRangeException;
            }
        }
        catch ( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/B', 404, '' );
        }
       
       
$topic->topic_archive_status = \IPS\forums\Topic::ARCHIVE_RESTORE;
       
$topic->save();

       
/* Log */
       
\IPS\Session::i()->modLog( 'modlog__unarchived_topic', array( $topic->url()->__toString() => FALSE, $topic->mapped( 'title' ) => FALSE ), $topic );
       
        \
IPS\Output::i()->redirect( $topic->url() );
    }

   
/**
     * Remove the archive exclude flag
     *
     * @return void
     */
   
public function removeArchiveExclude()
    {
        \
IPS\Session::i()->csrfCheck();

        try
        {
           
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
            if ( !
$topic->canRemoveArchiveExcludeFlag() )
            {
                throw new \
OutOfRangeException;
            }
        }
        catch ( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/P', 404, '' );
        }

       
$topic->topic_archive_status = \IPS\forums\Topic::ARCHIVE_NOT;
       
$topic->save();

       
/* Log */
       
\IPS\Session::i()->modLog( 'modlog__removed_archive_exclude_topic', array( $topic->url()->__toString() => FALSE, $topic->mapped( 'title' ) => FALSE ), $topic );

        \
IPS\Output::i()->redirect( $topic->url() );
    }
   
   
/**
     * Rate Question
     *
     * @return    void
     */
   
public function rateQuestion()
    {
       
/* CSRF Check */
       
\IPS\Session::i()->csrfCheck();
       
       
/* Get the question */
       
try
        {
           
$question = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
        }
        catch ( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/8', 404, '' );
        }
       
       
/* Voting up or down? */
       
$rating = intval( \IPS\Request::i()->rating );
        if (
$rating !== 1 and $rating !== -1 )
        {
            \
IPS\Output::i()->error( 'form_bad_value', '2F173/A', 403, '' );
        }
       
       
/* Check we can cast this vote */
       
if ( !$question->canVote( $rating ) )
        {
            \
IPS\Output::i()->error( 'no_module_permission', '2F173/9', 403, '' );
        }
       
       
/* If we have an existing vote, we're just undoing that, otherwise, insert the vote */
       
$ratings = $question->votes();
        if ( isset(
$ratings[ \IPS\Member::loggedIn()->member_id ] ) )
        {
            \
IPS\Db::i()->delete( 'forums_question_ratings', array( 'topic=? AND member=?', $question->tid, \IPS\Member::loggedIn()->member_id ) );
        }
        else
        {
            \
IPS\Db::i()->insert( 'forums_question_ratings', array(
               
'topic'        => $question->tid,
               
'forum'        => $question->forum_id,
               
'member'    => \IPS\Member::loggedIn()->member_id,
               
'rating'    => $rating,
               
'date'        => time()
            ),
TRUE );
        }
       
       
/* Rebuild count */
       
$question->question_rating = \IPS\Db::i()->select( 'SUM(rating)', 'forums_question_ratings', array( 'topic=?', $question->tid ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
       
$question->save();
       
       
/* Redirect back */
       
\IPS\Output::i()->redirect( $question->url() );
    }
   
   
/**
     * Rate Answer
     *
     * @return    void
     */
   
public function rateAnswer()
    {
        \
IPS\Session::i()->csrfCheck();
       
        try
        {
           
$question = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
           
$answer = \IPS\forums\Topic\Post::loadAndCheckPerms( \IPS\Request::i()->answer );
        }
        catch ( \
Exception $e )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/4', 404, '' );
        }
       
        if ( !
$answer->item()->can('read') or !$answer->canVote() )
        {
            \
IPS\Output::i()->error( 'no_module_permission', '2F173/5', 403, '' );
        }
       
       
$rating = intval( \IPS\Request::i()->rating );
        if (
$rating !== 1 and $rating !== -1 )
        {
            \
IPS\Output::i()->error( 'form_bad_value', '2F173/6', 403, '' );
        }

       
/* Have we already rated ? */
       
try
        {
           
$rating = \IPS\Db::i()->select( '*', 'forums_answer_ratings', array( 'topic=? AND post=? AND member=?', $question->tid, $answer->pid, \IPS\Member::loggedIn()->member_id ) )->first();
            \
IPS\Db::i()->delete( 'forums_answer_ratings', array( 'topic=? AND post=? AND member=?', $question->tid, $answer->pid, \IPS\Member::loggedIn()->member_id ) );
        }
        catch ( \
UnderflowException $e )
        {
            \
IPS\Db::i()->insert( 'forums_answer_ratings', array(
               
'post'        => $answer->pid,
               
'topic'        => $question->tid,
               
'member'    => \IPS\Member::loggedIn()->member_id,
               
'rating'    => $rating,
               
'date'        => time()
            ),
TRUE );
        }

       
$answer->post_field_int = (int) \IPS\Db::i()->select( 'SUM(rating)', 'forums_answer_ratings', array( 'post=?', $answer->pid ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
       
$answer->save();

        if ( \
IPS\Request::i()->isAjax() )
        {
            \
IPS\Output::i()->json( array( 'votes' => $answer->post_field_int, 'canVoteUp' => $answer->canVote(1), 'canVoteDown' => $answer->canVote(-1) ) );
        }
        else
        {
            \
IPS\Output::i()->redirect( $answer->url() );
        }
    }
   
   
/**
     * Set Best Answer
     *
     * @return    void
     */
   
public function bestAnswer()
    {
        \
IPS\Session::i()->csrfCheck();
       
        try
        {
           
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
            if ( !
$topic->canSetBestAnswer() )
            {
                throw new \
OutOfRangeException;
            }
           
           
$post = \IPS\forums\Topic\Post::loadAndCheckPerms( \IPS\Request::i()->answer );
            if (
$post->item() != $topic )
            {
                throw new \
OutOfRangeException;
            }
        }
        catch ( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/7', 404, '' );
        }
       
        if (
$topic->topic_answered_pid )
        {
            try
            {
               
$oldBestAnswer = \IPS\forums\Topic\Post::load( $topic->topic_answered_pid );
               
$oldBestAnswer->post_bwoptions['best_answer'] = FALSE;
               
$oldBestAnswer->save();
            }
            catch ( \
Exception $e ) {}
        }
       
       
$post->post_bwoptions['best_answer'] = TRUE;
       
$post->save();
       
       
$topic->topic_answered_pid = $post->pid;
       
$topic->save();
       
       
/* Log */
       
if ( \IPS\Member::loggedIn()->modPermission('can_set_best_answer') )
        {
            \
IPS\Session::i()->modLog( 'modlog__best_answer_set', array( $post->pid => FALSE ), $topic );
        }
       
        \
IPS\Output::i()->redirect( $topic->url() );
    }
   
   
/**
     * Unset Best Answer
     *
     * @return    void
     */
   
public function unsetBestAnswer()
    {
        \
IPS\Session::i()->csrfCheck();
       
        try
        {
           
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
            if ( !
$topic->canSetBestAnswer() )
            {
                throw new \
OutOfRangeException;
            }
        }
        catch ( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/G', 404, '' );
        }
   
        if (
$topic->topic_answered_pid )
        {
            try
            {
               
$post = \IPS\forums\Topic\Post::load( $topic->topic_answered_pid );
               
$post->post_bwoptions['best_answer'] = FALSE;
               
$post->save();
               
                if ( \
IPS\Member::loggedIn()->modPermission('can_set_best_answer') )
                {
                    \
IPS\Session::i()->modLog( 'modlog__best_answer_unset', array( $topic->topic_answered_pid => FALSE ), $topic );
                }
            }
            catch ( \
Exception $e ) {}
        }
   
       
$topic->topic_answered_pid = FALSE;
       
$topic->save();
   
        \
IPS\Output::i()->redirect( $topic->url() );
    }
   
   
/**
     * Saved Action
     *
     * @return    void
     */
   
public function savedAction()
    {
        try
        {
            \
IPS\Session::i()->csrfCheck();
           
           
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
           
$action = \IPS\forums\SavedAction::load( \IPS\Request::i()->action );
           
$action->runOn( $topic );
           
           
/* Log */
           
\IPS\Session::i()->modLog( 'modlog__saved_action', array( 'forums_mmod_' . $action->mm_id => TRUE, $topic->url()->__toString() => FALSE, $topic->mapped( 'title' ) => FALSE ), $topic );
            \
IPS\Output::i()->redirect( $topic->url() );
        }
        catch ( \
LogicException $e )
        {
           
        }
    }

   
/**
     * Mark Topic Read
     *
     * @return    void
     */
   
public function markRead()
    {
        \
IPS\Session::i()->csrfCheck();
       
        try
        {
           
$topic = \IPS\forums\Topic::load( \IPS\Request::i()->id );
           
$topic->markRead();

            if ( \
IPS\Request::i()->isAjax() )
            {
                \
IPS\Output::i()->json( "OK" );
            }
            else
            {
                \
IPS\Output::i()->redirect( $topic->url() );
            }
        }
        catch ( \
OutOfRangeException $e )
        {
            \
IPS\Output::i()->error( 'no_module_permission', '2F173/C', 403, 'no_module_permission_guest' );
        }
    }
   
   
/**
     * We need to use the custom widget poll template for ajax methods
     *
     * @return void
     */
   
public function widgetPoll()
    {
        try
        {
           
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
        }
        catch( \
OutOfRangeException $ex )
        {
            \
IPS\Output::i()->error( 'node_error', '2F173/N', 403, '' );
        }
       
       
$poll  = $topic->getPoll();
       
$poll->displayTemplate = array( \IPS\Theme::i()->getTemplate( 'widgets', 'forums', 'front' ), 'pollWidget' );
       
$poll->url = $topic->url();
       
        \
IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'widgets', 'forums', 'front' )->poll( $topic, $poll );
    }
   
   
/**
     * Find a Comment / Review (do=findComment/findReview)
     *
     * @param    string                    $commentClass    The comment/review class
     * @param    \IPS\Content\Comment    $comment        The comment/review
     * @param    \IPS\Content\Item        $item            The item
     * @return    void
     */
   
public function _find( $commentClass, $comment, $item )
    {
       
/* For normal topics (i.e. not questions), we can handle this normally */
       
if ( !$item->isQuestion() )
        {
            return
parent::_find( $commentClass, $comment, $item );
        }

       
/* Otherwise we need to get the position ordered by votes... */
       
if ( $item->isArchived() )
        {
           
$where = array( array( 'archive_topic_id=?', $item->tid ) );
            if ( !
$item->canViewHiddenComments() )
            {
               
$hiddenWhereClause = "(archive_queued != -2 AND archive_queued != -1 AND archive_queued != 1)";

                if ( \
IPS\Member::loggedIn()->member_id )
                {
                   
$where[] = array( "( {$hiddenWhereClause} OR ( archive_queued=1 AND archive_author_id=" . \IPS\Member::loggedIn()->member_id . '))' );
                }
                else
                {
                   
$where[] = array( $hiddenWhereClause );
                }
            }
           
$answers = \IPS\Db::i()->select( 'archive_id, @rownum := @rownum + 1 AS position', 'forums_archive_posts', $where, 'archive_is_first DESC, archive_bwoptions DESC, archive_field_int DESC, archive_content_date' )->join( array( '(SELECT @rownum := 0)', 'r' ), NULL, 'JOIN' );
           
$commentPosition = \IPS\Db::i()->select( 'position', $answers, array( 'archive_id=?', $comment->id ) )->first() - 1;
        }
        else
        {
           
$where = array( array( 'topic_id=?', $item->tid ) );
            if ( !
$item->canViewHiddenComments() )
            {
               
$hiddenWhereClause = "(queued != -2 AND queued != -1 AND queued != 1)";

                if ( \
IPS\Member::loggedIn()->member_id )
                {
                   
$where[] = array( "( {$hiddenWhereClause} OR ( queued=1 AND author_id=" . \IPS\Member::loggedIn()->member_id . '))' );
                }
                else
                {
                   
$where[] = array( $hiddenWhereClause );
                }
            }

           
$answers = \IPS\Db::i()->select( 'pid, @rownum := @rownum + 1 AS position', 'forums_posts', $where, 'new_topic DESC, post_bwoptions DESC, post_field_int DESC, post_date' )->join( array( '(SELECT @rownum := 0)', 'r' ), NULL, 'JOIN' );
           
$commentPosition = \IPS\Db::i()->select( 'position', $answers, array( 'pid=?', $comment->pid ) )->first() - 1;
        }

       
/* Now work out what page that makes it */
       
$url = $item->url();
       
$perPage = $item::getCommentsPerPage();
       
$page = ceil( $commentPosition / $perPage );
        if (
$page != 1 )
        {
           
$url = $url->setQueryString( 'page', $page );
        }

       
/* And redirect */
       
$idField = $commentClass::$databaseColumnId;
        \
IPS\Output::i()->redirect( $url->setFragment( 'comment-' . $comment->$idField ) );
    }

   
/**
     * Edit Comment/Review
     *
     * @param    string                    $commentClass    The comment/review class
     * @param    \IPS\Content\Comment    $comment        The comment/review
     * @param    \IPS\Content\Item        $item            The item
     * @return    void
     * @throws    \LogicException
     */
   
protected function _edit( $commentClass, $comment, $item )
    {
        \
IPS\Member::loggedIn()->language()->words['edit_comment']        = \IPS\Member::loggedIn()->language()->addToStack( 'edit_reply', FALSE );

        return
parent::_edit( $commentClass, $comment, $item );
    }
   
   
/**
     * Stuff that applies to both comments and reviews
     *
     * @param    string    $method    Desired method
     * @param    array    $args    Arguments
     * @return    void
     */
   
public function __call( $method, $args )
    {
       
$class = static::$contentModel;
       
        try
        {
           
$item = $class::load( \IPS\Request::i()->id );
            if ( !
$item->canView() )
            {
               
$forum = $item->container();
                \
IPS\Output::i()->error( $forum ? $forum->errorMessage() : 'node_error_no_perm', '2F173/K', 403, '' );
            }
           
            if (
$item->isArchived() )
            {
               
$class::$commentClass = $class::$archiveClass;
            }
           
            return
parent::__call( $method, $args );
        }
        catch( \
OutOfRangeException $e )
        {
            if ( isset( \
IPS\Request::i()->do ) AND \IPS\Request::i()->do === 'findComment' AND isset( \IPS\Request::i()->comment ) )
            {
                try
                {
                   
$commentClass = $class::$commentClass;
                   
$comment = $commentClass::load( \IPS\Request::i()->comment );
                   
$topic   = \IPS\forums\Topic::load( $comment->topic_id );
                   
                    \
IPS\Output::i()->redirect( $topic->url()->setQueryString( array( 'do' => 'findComment', 'comment' => \IPS\Request::i()->comment ) ), NULL, 301 );
                }
                catch( \
Exception $e )
                {
                    \
IPS\Output::i()->error( 'node_error', '2F173/M', 404, '' );
                }
            }
        }
        catch ( \
Exception $e )
        {
            \
IPS\Log::log( $e, 'topic_call' );
            \
IPS\Output::i()->error( 'node_error', '2F173/I', 404, '' );
        }
    }
   
   
/**
     * Form for splitting
     *
     * @param    \IPS\Content\Item    $item    The item
     * @return    \IPS\Helpers\Form
     */
   
protected function _splitForm( \IPS\Content\Item $item )
    {
       
$form = parent::_splitForm( $item );

        if ( isset(
$form->elements['']['topic_create_state'] ) )
        {
            unset(
$form->elements['']['topic_create_state'] );
        }
       
        return
$form;
    }
}