1044 lines
31 KiB
PHP
1044 lines
31 KiB
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\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;
|
|
}
|
|
} |