<?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;
}
}