Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

@@ -12,7 +12,7 @@
namespace IPS\forums\modules\front\forums;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -12,7 +12,7 @@
namespace IPS\forums\modules\front\forums;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -23,13 +23,15 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _forums extends \IPS\Dispatcher\Controller
{
protected $themeGroup = NULL;
/**
* Route
*
* @return void
*/
protected function manage()
{
{
$forum = NULL;
try
{
@@ -49,6 +51,10 @@ class _forums extends \IPS\Dispatcher\Controller
*/
public function _forum( $forum )
{
$this->themeGroup = \IPS\Theme::i()->getTemplate( 'forums', 'forums', 'front' );
$forum->clubCheckRules();
/* Is simple mode on? If so, redirect to the index page */
if ( \IPS\forums\Forum::isSimpleView( $forum ) )
{
@@ -77,31 +83,40 @@ class _forums extends \IPS\Dispatcher\Controller
/* Users can see topics posted by other users? */
$where = array();
if ( !$forum->can_view_others and !\IPS\Member::loggedIn()->modPermission( 'can_read_all_topics' ) )
if ( !$forum->memberCanAccessOthersTopics( \IPS\Member::loggedIn() ) )
{
$where[] = array( 'starter_id = ?', \IPS\Member::loggedIn()->member_id );
}
$getReactions = $getFirstComment = $getFollowerCount = (boolean) ( \IPS\forums\Forum::getMemberListView() === 'snippet' );
/* Init table (it won't show anything until after the password check, but it sets navigation and titles) */
$table = new \IPS\Helpers\Table\Content( 'IPS\forums\Topic', $forum->url(), $where, $forum, NULL, 'view', isset( \IPS\Request::i()->rss ) ? FALSE : TRUE, isset( \IPS\Request::i()->rss ) ? FALSE : TRUE );
$table = new \IPS\Helpers\Table\Content( 'IPS\forums\Topic', $forum->url(), $where, $forum, NULL, 'view', isset( \IPS\Request::i()->rss ) ? FALSE : TRUE, isset( \IPS\Request::i()->rss ) ? FALSE : TRUE, NULL, $getFirstComment, $getFollowerCount, $getReactions );
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'forums', 'forums', 'front' ), 'forumTable' );
$table->classes = array( 'cTopicList' );
$table->limit = \IPS\Settings::i()->forums_topics_per_page;
$table->title = \IPS\Member::loggedIn()->language()->addToStack( ( $forum->forums_bitoptions['bw_enable_answers'] ) ? 'count_questions_in_forum' : 'count_topics_in_forum', FALSE, array( 'pluralize' => array( $forum->topics ) ) );
if ( $forum->forums_bitoptions['bw_enable_answers'] )
{
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'forums', 'forums', 'front' ), 'questionRow' );
$table->rowsTemplate = array( $this->themeGroup, 'questionRow' );
}
else
{
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'forums', 'forums', 'front' ), 'topicRow' );
if ( \IPS\forums\Forum::getMemberListView() == 'snippet' )
{
$table->rowsTemplate = array( $this->themeGroup, 'topicRowSnippet' );
}
else
{
$table->rowsTemplate = array( $this->themeGroup, 'topicRow' );
}
}
$table->hover = TRUE;
$table->sortOptions['num_replies'] = $table->sortOptions['num_comments'];
unset( $table->sortOptions['num_comments'] );
/* If there's only one forum we actually don't want the nav */
if ( \IPS\forums\Forum::theOnlyForum() )
/* If there's only one forum and we're not in a club, we actually don't want the nav */
if ( \IPS\forums\Forum::theOnlyForum() AND !$forum->club() )
{
\IPS\Output::i()->breadcrumb = isset( \IPS\Output::i()->breadcrumb['module'] ) ? array( 'module' => \IPS\Output::i()->breadcrumb['module'] ) : array();
}
@@ -158,6 +173,15 @@ class _forums extends \IPS\Dispatcher\Controller
$table->sortOptions['question_rating'] = 'forums_topics.question_rating';
}
else if ( $forum->forums_bitoptions['bw_enable_answers_moderator'] )
{
$table->filters = array(
'solved_topics' => 'topic_answered_pid>0',
'unsolved_topics' => 'topic_answered_pid=0',
);
$table->sortOptions['question_rating'] = 'forums_topics.question_rating';
}
/* Are we a moderator? */
if( \IPS\forums\Topic::modPermission( 'unhide', NULL, $forum ) )
@@ -318,6 +342,14 @@ class _forums extends \IPS\Dispatcher\Controller
{
/* Show the link */
$rssUrl = \IPS\Http\Url::internal( "app=forums&module=forums&controller=forums&id={$forum->_id}&rss=1", 'front', 'forums_rss', array( $forum->name_seo ) );
if ( \IPS\Member::loggedIn()->member_id )
{
$key = md5( ( \IPS\Member::loggedIn()->members_pass_hash ?: \IPS\Member::loggedIn()->email ) . \IPS\Member::loggedIn()->members_pass_salt );
$rssUrl = $rssUrl->setQueryString( array( 'member' => \IPS\Member::loggedIn()->member_id , 'key' => $key ) );
}
if ( $forum->forums_bitoptions['bw_enable_answers'] )
{
$rssTitle = \IPS\Member::loggedIn()->language()->addToStack( 'forum_rss_title_questions', FALSE, array( 'escape' => true, 'sprintf' => array( $forum->_title ) ) );
@@ -327,49 +359,17 @@ class _forums extends \IPS\Dispatcher\Controller
$rssTitle = \IPS\Member::loggedIn()->language()->addToStack( 'forum_rss_title_topics', FALSE, array( 'escape' => true, 'sprintf' => array( $forum->_title ) ) );
}
\IPS\Output::i()->rssFeeds[ $rssTitle ] = $rssUrl;
/* Or actually show RSS feed */
if ( isset( \IPS\Request::i()->rss ) )
{
/* Set the title, either topics or questions */
if ( $forum->forums_bitoptions['bw_enable_answers'] )
{
$rssTitle = sprintf( \IPS\Member::loggedIn()->language()->get( 'forum_rss_title_questions'), \IPS\Member::loggedIn()->language()->get( "forums_forum_{$forum->id}" ) );
}
else
{
$rssTitle = sprintf( \IPS\Member::loggedIn()->language()->get( 'forum_rss_title_topics'), \IPS\Member::loggedIn()->language()->get( "forums_forum_{$forum->id}" ) );
}
/* Don't include "moved" links in the RSS feed, as there is no content to include ( $topic->content() results in BadMethodCallException) */
$table->where[] = array( 'forums_topics.moved_to IS NULL' );
/* Can we view the content (permission_showtopic may allow us to view the list, but not the content)? */
$canViewContent = $forum->can('read');
/* Build the document */
$document = \IPS\Xml\Rss::newDocument( $forum->url(), $rssTitle, $rssTitle );
foreach ( $table->getRows( array() ) as $topic )
{
if ( !$topic->hidden() )
{
try
{
$document->addItem( $topic->title, $topic->url(), $canViewContent ? $topic->content() : NULL, \IPS\DateTime::ts( $topic->start_date ), $topic->tid );
}
catch ( \Exception $e ) { }
}
}
/* Display - note application/rss+xml is not a registered IANA mime-type so we need to stick with text/xml for RSS */
\IPS\Output::i()->sendOutput( $document->asXML(), 200, 'text/xml' );
}
}
/* Online User Location */
$permissions = $forum->permissions();
\IPS\Session::i()->setLocation( $forum->url(), explode( ",", $permissions['perm_view'] ), 'loc_forums_viewing_forum', array( "forums_forum_{$forum->id}" => TRUE ) );
if ( \IPS\forums\Forum::getMemberView() === 'grid' )
{
\IPS\forums\Forum::populateFollowerCounts( $forum );
}
/* Show Forum */
if ( isset( \IPS\Request::i()->advancedSearchForm ) )
{
@@ -408,8 +408,8 @@ class _forums extends \IPS\Dispatcher\Controller
{
$forumOutput = (string) $table;
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'forums' )->forumDisplay( $forum, $forumOutput );
\IPS\Output::i()->output = $this->themeGroup->forumDisplay( $forum, $forumOutput );
}
/**
@@ -465,20 +465,24 @@ class _forums extends \IPS\Dispatcher\Controller
$form = \IPS\forums\Topic::create( $forum );
$hasModOptions = false;
$canHide = ( \IPS\Member::loggedIn()->group['g_hide_own_posts'] == '1' or \in_array( 'IPS\forums\Topics', explode( ',', \IPS\Member::loggedIn()->group['g_hide_own_posts'] ) ) );
if ( \IPS\forums\Topic::modPermission( 'lock', NULL, $forum ) or
\IPS\forums\Topic::modPermission( 'pin', NULL, $forum ) or
\IPS\forums\Topic::modPermission( 'hide', NULL, $forum ) or
$canHide or
\IPS\forums\Topic::modPermission( 'feature', NULL, $forum ) )
{
$hasModOptions = TRUE;
}
$formTemplate = $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'submit', 'forums' ) ), 'createTopicForm' ), $forum, $hasModOptions, NULL );
$formTemplate = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'submit', 'forums' ), 'createTopicForm' ), $forum, $hasModOptions, NULL );
if ( \IPS\forums\Topic::moderateNewItems( \IPS\Member::loggedIn(), $forum ) )
$guestPostBeforeRegister = ( !\IPS\Member::loggedIn()->member_id ) ? !$forum->can( 'add', \IPS\Member::loggedIn(), FALSE ) : NULL;
$modQueued = \IPS\forums\Topic::moderateNewItems( \IPS\Member::loggedIn(), $forum, $guestPostBeforeRegister );
if ( $guestPostBeforeRegister or $modQueued )
{
$formTemplate = \IPS\Theme::i()->getTemplate( 'forms', 'core' )->modQueueMessage( \IPS\Member::loggedIn()->warnings( 5, NULL, 'mq' ), \IPS\Member::loggedIn()->mod_posts ) . $formTemplate;
$formTemplate = \IPS\Theme::i()->getTemplate( 'forms', 'core' )->postingInformation( $guestPostBeforeRegister, $modQueued, TRUE ) . $formTemplate;
}
$title = $forum->forums_bitoptions['bw_enable_answers'] ? 'ask_new_question' : 'create_new_topic';
@@ -564,6 +568,27 @@ class _forums extends \IPS\Dispatcher\Controller
\IPS\Output::i()->error( 'no_module_permission', '2F173/3', 403, 'no_module_permission_guest' );
}
}
/**
* Set the viewing method
*
* @return void
*/
protected function setMethod()
{
\IPS\Session::i()->csrfCheck();
$method = ( isset( \IPS\Request::i()->method ) ) ? \IPS\Request::i()->method : \IPS\Settings::i()->forums_default_view;
\IPS\Request::i()->setCookie( 'forum_list_view', $method, ( new \IPS\DateTime )->add( new \DateInterval( 'P1Y' ) ) );
if ( \IPS\Member::loggedIn()->member_id )
{
\IPS\Db::i()->replace( 'forums_view_method', array( 'member_id' => \IPS\Member::loggedIn()->member_id, 'method' => $method, 'type' => 'list' ) );
}
\IPS\Output::i()->redirect( \IPS\forums\Forum::load( \IPS\Request::i()->id )->url() );
}
/**
* Shows the forum selector for creating a topic outside of specific forum
@@ -603,4 +628,4 @@ class _forums extends \IPS\Dispatcher\Controller
\IPS\Output::i()->breadcrumb[] = array( NULL, \IPS\Member::loggedIn()->language()->addToStack( 'select_forum' ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'forums' )->forumSelector( $form );
}
}
}
@@ -12,7 +12,7 @@
namespace IPS\forums\modules\front\forums;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -116,7 +116,7 @@ class _index extends \IPS\Dispatcher\Controller
if ( \IPS\forums\Forum::isSimpleView() )
{
$where = array( 'container' => array( array( 'forums_forums.password IS NULL' ) ) );
if ( !\IPS\Settings::i()->club_nodes_in_apps )
if ( !\IPS\Settings::i()->club_nodes_in_apps OR !\IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'clubs', 'front' ) ) )
{
$where['container'][] = array( 'forums_forums.club_id IS NULL' );
}
@@ -134,7 +134,7 @@ class _index extends \IPS\Dispatcher\Controller
}
/* Inline forum IDs? */
if ( count( $ids ) )
if ( \count( $ids ) )
{
foreach( $ids as $id )
{
@@ -142,7 +142,7 @@ class _index extends \IPS\Dispatcher\Controller
{
if ( $id == 'clubs' )
{
$parentIds['clubs'] = 'clubs';
$map['clubs'] = 'clubs';
foreach ( \IPS\forums\Forum::clubNodes() as $child )
{
$forumIds[ $child->id ] = $child->id;
@@ -160,7 +160,7 @@ class _index extends \IPS\Dispatcher\Controller
catch( \Exception $ex ) { }
}
if ( count( $forumIds ) )
if ( \count( $forumIds ) )
{
$where['container'][] = array( \IPS\Db::i()->in( 'forums_forums.id', array_filter( $forumIds ) ) );
}
@@ -181,7 +181,7 @@ class _index extends \IPS\Dispatcher\Controller
if( \IPS\forums\Forum::theOnlyForum() === NULL )
{
\IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate( 'index' )->simplifiedViewSidebar( $forumIds );
\IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate( 'index' )->simplifiedViewSidebar( $forumIds, $map );
}
if ( isset( $where['container'] ) )
@@ -192,7 +192,12 @@ class _index extends \IPS\Dispatcher\Controller
}
else
{
/* Standard view */
/* Merge in follower counts to the immediately visible forums */
if ( \IPS\forums\Forum::getMemberView() === 'grid' )
{
\IPS\forums\Forum::populateFollowerCounts();
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'index' )->index();
}
}
@@ -212,7 +217,7 @@ class _index extends \IPS\Dispatcher\Controller
if ( \IPS\Member::loggedIn()->member_id )
{
\IPS\Db::i()->replace( 'forums_view_method', array( 'member_id' => \IPS\Member::loggedIn()->member_id, 'method' => $method ) );
\IPS\Db::i()->replace( 'forums_view_method', array( 'member_id' => \IPS\Member::loggedIn()->member_id, 'method' => $method, 'type' => 'index' ) );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=forums&module=forums&controller=index', 'front', 'forums' ) );
+257 -134
View File
@@ -12,7 +12,7 @@
namespace IPS\forums\modules\front\forums;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -42,21 +42,14 @@ class _topic extends \IPS\Content\Controller
/**
* View Topic
*
* @return void
* @return void
* @throws \Exception
*/
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 )
@@ -91,6 +84,16 @@ class _topic extends \IPS\Content\Controller
}
}
$topic->container()->clubCheckRules();
/* If there's only one forum and we're not in a club, we actually don't want the nav */
if ( !$topic->container()->club() AND ( \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;
}
/* Legacy findpost redirect */
if ( \IPS\Request::i()->findpost )
{
@@ -110,6 +113,18 @@ class _topic extends \IPS\Content\Controller
$this->_doViewCheck();
}
/* If the topic is locked and scheduled to unlock already, or vice versa, do that */
if( $topic->locked() && $topic->topic_open_time && $topic->topic_open_time < time() )
{
$topic->state = 'open';
$topic->save();
}
elseif( !$topic->locked() && $topic->topic_close_time && $topic->topic_close_time < time() )
{
$topic->state = 'closed';
$topic->save();
}
/* 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. */
@@ -127,33 +142,51 @@ class _topic extends \IPS\Content\Controller
{
$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 )
/* If this topic was moved or merged, load that up in case someone loads the preview after that happens but before they reload the page */
$previewTopic = $topic;
if ( \in_array( $topic->state, array( 'merged', 'link' ) ) )
{
$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 )
$movedTo = explode( '&', $topic->moved_to );
try
{
$firstUnread = $topic->comments( 1, NULL, 'date', 'asc', NULL, NULL, $timeLastRead );
$previewTopic = \IPS\forums\Topic::loadAndCheckPerms( $movedTo[0] );
}
catch( \OutOfRangeException $e )
{
/* I can't help you */
\IPS\Output::i()->error( 'node_error', '2F173/Q', 404, '' );
return;
}
}
$firstPost = $postClass::load( $previewTopic->topic_firstpost );
$topicOverview = array( 'firstPost' => array( $previewTopic->isQuestion() ? 'question_mainTab' : 'first_post', $firstPost ) );
if ( $previewTopic->posts > 1 )
{
$latestPost = $previewTopic->comments( 1, 0, 'date', 'DESC' );
$topicOverview['latestPost'] = array( $previewTopic->isQuestion() ? 'latest_answer' : 'latest_post', $latestPost );
$timeLastRead = $previewTopic->timeLastRead();
if ( $timeLastRead instanceof \IPS\DateTime AND $previewTopic->unread() !== 0 )
{
$firstUnread = $previewTopic->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 ) );
$topicOverview['firstUnread'] = array( 'first_unread_post_hover', $previewTopic->comments( 1, NULL, 'date', 'asc', NULL, NULL, $timeLastRead ) );
}
}
}
if ( $topic->isQuestion() and $topic->topic_answered_pid )
if ( $previewTopic->isQuestion() and $previewTopic->topic_answered_pid )
{
$topicOverview['bestAnswer'] = array( 'best_answer_post', \IPS\forums\Topic\Post::load( $topic->topic_answered_pid ) );
$topicOverview['bestAnswer'] = array( 'best_answer_post', \IPS\forums\Topic\Post::load( $previewTopic->topic_answered_pid ) );
}
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'forums' )->topicHover( $topic, $topicOverview ) );
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'forums' )->topicHover( $previewTopic, $topicOverview ) );
return;
}
@@ -201,7 +234,7 @@ class _topic extends \IPS\Content\Controller
}
catch ( \UnderflowException $e ) { }
$page = ( isset( \IPS\Request::i()->page ) ) ? intval( \IPS\Request::i()->page ) : 1;
$page = ( isset( \IPS\Request::i()->page ) ) ? \intval( \IPS\Request::i()->page ) : 1;
if( $page < 1 )
{
@@ -230,18 +263,13 @@ class _topic extends \IPS\Content\Controller
$current = current( $comments );
reset( $comments );
if( !count( $comments ) AND !$topic->isQuestion() )
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() )
if( !$topic->isLastPage() AND $topic->unread() !== 0 )
{
$maxTime = 0;
@@ -250,7 +278,10 @@ class _topic extends \IPS\Content\Controller
$maxTime = ( $comment->mapped('date') > $maxTime ) ? $comment->mapped('date') : $maxTime;
}
$topic->markRead( NULL, $maxTime );
if( $topic->timeLastRead() === NULL OR $maxTime > $topic->timeLastRead()->getTimestamp() )
{
$topic->markRead( NULL, $maxTime );
}
}
$votes = array();
@@ -267,79 +298,63 @@ class _topic extends \IPS\Content\Controller
{
\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();
$nextUnread = $topic->containerHasUnread();
}
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() )
/* Sidebar? */
if ( $topic->showSummaryOnDesktop() == 'sidebar' )
{
\IPS\Output::i()->jsonLd['topic']['aggregateRating'] = array(
'@type' => 'AggregateRating',
'ratingValue' => $topic->averageRating(),
'ratingCount' => $topic->numberOfRatings(),
);
\IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate( 'topics' )->activity( $topic, 'sidebar' );
}
/* Add Json-LD */
$isQuestion = ( $topic->isQuestion() or $topic->isSolved() );
\IPS\Output::i()->jsonLd['topic'] = array(
'@context' => "http://schema.org",
'@type' => $isQuestion ? 'Question' : 'DiscussionForumPosting',
'@id' => (string) $topic->url(),
'isPartOf' => array(
'@id' => \IPS\Settings::i()->base_url . '#website'
),
'publisher' => array(
'@id' => \IPS\Settings::i()->base_url . '#organization'
),
'url' => (string) $topic->url(),
'discussionUrl' => (string) $topic->url(),
'mainEntityOfPage' => array(
'@type' => 'WebPage',
'@id' => (string) $topic->url()
)
);
/* 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();
\IPS\Output::i()->jsonLd['topic']['author']['url'] = (string) \IPS\Member::load( $topic->starter_id )->url();
\IPS\Output::i()->jsonLd['topic']['publisher']['member'] = array(
'@type' => "Person",
'name' => \IPS\Member::load( $topic->starter_id )->name,
'image' => (string) \IPS\Member::load( $topic->starter_id )->get_photo(),
'url' => (string) \IPS\Member::load( $topic->starter_id )->url(),
);
}
if( $topic->isQuestion() )
if( $isQuestion )
{
\IPS\Output::i()->jsonLd['topic']['answerCount'] = $topic->posts ? $topic->posts - 1 : 0;
\IPS\Output::i()->jsonLd['topic']['upvoteCount'] = intval( $topic->question_rating );
\IPS\Output::i()->jsonLd['topic']['upvoteCount'] = \intval( $topic->question_rating );
if( $topic->topic_answered_pid )
{
@@ -349,9 +364,8 @@ class _topic extends \IPS\Content\Controller
\IPS\Output::i()->jsonLd['topic']['acceptedAnswer'] = array(
'@type' => 'Answer',
'text' => strip_tags( $answer->truncated() ),
'text' => $answer->truncated( TRUE, NULL ),
'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(
@@ -376,17 +390,24 @@ class _topic extends \IPS\Content\Controller
$i = 0;
foreach( $comments as $comment )
{
// Don't include the first post as a "comment"
if( $comment->pid == $topic->topic_firstpost )
{
continue;
}
$url = $topic->url()->setPage( 'page', \IPS\Request::i()->page );
\IPS\Output::i()->jsonLd['topic']['comment'][ $i ] = array(
'@type' => 'Comment',
'url' => (string) $comment->url(),
'@id' => (string) $url->setFragment( 'comment-' . $comment->pid ),
'url' => (string) $url->setFragment( 'comment-' . $comment->pid ),
'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(),
'text' => $comment->truncated( TRUE, NULL ),
);
if( $comment->author_id )
@@ -398,7 +419,43 @@ class _topic extends \IPS\Content\Controller
}
}
\IPS\Output::i()->jsonLd['topic'] = array_merge_recursive( array(
'name' => $topic->mapped('title'),
'headline' => $topic->mapped('title'),
'text' => $topic->isQuestion() ? $question->truncated( TRUE, NULL ) : $current->truncated( TRUE, NULL ),
'dateCreated' => \IPS\DateTime::ts( $topic->start_date )->format( \IPS\DateTime::ISO8601 ),
'datePublished' => \IPS\DateTime::ts( $topic->start_date )->format( \IPS\DateTime::ISO8601 ),
'dateModified' => \IPS\DateTime::ts( $topic->last_post )->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\Member::load( $topic->starter_id )->get_photo(),
'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 - 1 // We subtract one to account for the "first post"
),
array(
'@type' => 'InteractionCounter',
'interactionType' => "http://schema.org/FollowAction",
'userInteractionCount' => $topic->followersCount()
),
)
), \IPS\Output::i()->jsonLd['topic'] );
/* Show topic */
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'topics.css', 'forums' ) );
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'topics' )->topic( $topic, $comments, $question, $votes, $nextUnread, $pagination, $topicVotes );
}
@@ -474,9 +531,8 @@ class _topic extends \IPS\Content\Controller
$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 )
foreach( array( 'lock', 'pin', 'feature' ) AS $perm )
{
if ( $class::modPermission( $perm, NULL, $forum ) )
{
@@ -484,19 +540,33 @@ class _topic extends \IPS\Content\Controller
break;
}
}
if( $topic->canHide() )
{
$hasModOptions = TRUE;
}
$form = $topic->buildEditForm();
if ( $values = $form->values() )
{
if ( $topic->canEdit() )
{
{
$titleField = $topic::$databaseColumnMap['title'];
$oldTitle = $topic->$titleField;
$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 );
$toLog = array( $topic::$title => FALSE, $topic->url()->__toString() => FALSE, $topic::$title => TRUE, $topic->mapped( 'title' ) => FALSE );
if ( $oldTitle != $topic->$titleField )
{
$toLog[ $oldTitle ] = false;
}
\IPS\Session::i()->modLog( 'modlog__item_edit', $toLog, $topic );
\IPS\Output::i()->redirect( $topic->url() );
}
@@ -506,7 +576,7 @@ class _topic extends \IPS\Content\Controller
}
}
$formTemplate = $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'submit', 'forums' ) ), 'createTopicForm' ), $forum, $hasModOptions, $topic );
$formTemplate = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'submit', 'forums' ), 'createTopicForm' ), $forum, $hasModOptions, $topic );
$title = $forum->forums_bitoptions['bw_enable_answers'] ? 'edit_question' : 'edit_topic';
@@ -614,7 +684,7 @@ class _topic extends \IPS\Content\Controller
}
/* Voting up or down? */
$rating = intval( \IPS\Request::i()->rating );
$rating = \intval( \IPS\Request::i()->rating );
if ( $rating !== 1 and $rating !== -1 )
{
\IPS\Output::i()->error( 'form_bad_value', '2F173/A', 403, '' );
@@ -626,13 +696,15 @@ class _topic extends \IPS\Content\Controller
\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 */
/* If we have an existing vote, remove it first */
$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 ) );
\IPS\Db::i()->delete( 'forums_question_ratings', array( 'topic=? AND `member`=?', $question->tid, \IPS\Member::loggedIn()->member_id ) );
}
else
/* Revoting for the same thing you already voted for should remove your vote - so don't insert if we voted for the same thing we did before */
if ( !isset( $ratings[ \IPS\Member::loggedIn()->member_id ] ) OR $ratings[ \IPS\Member::loggedIn()->member_id ] != $rating )
{
\IPS\Db::i()->insert( 'forums_question_ratings', array(
'topic' => $question->tid,
@@ -675,19 +747,19 @@ class _topic extends \IPS\Content\Controller
\IPS\Output::i()->error( 'no_module_permission', '2F173/5', 403, '' );
}
$rating = intval( \IPS\Request::i()->rating );
$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 )
$ratings = $question->answerVotes( \IPS\Member::loggedIn() );
/* If we've already rated the answer, remove that first */
\IPS\Db::i()->delete( 'forums_answer_ratings', array( 'topic=? AND post=? AND `member`=?', $question->tid, $answer->pid, \IPS\Member::loggedIn()->member_id ) );
/* Revoting for the same thing you already voted for should remove your vote - so don't insert if we voted for the same thing we did before */
if ( !isset( $ratings[ $answer->pid ] ) OR $ratings[ $answer->pid ] != $rating )
{
\IPS\Db::i()->insert( 'forums_answer_ratings', array(
'post' => $answer->pid,
@@ -723,12 +795,13 @@ class _topic extends \IPS\Content\Controller
try
{
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
$post = \IPS\forums\Topic\Post::loadAndCheckPerms( \IPS\Request::i()->answer );
if ( !$topic->canSetBestAnswer() )
{
throw new \OutOfRangeException;
}
$post = \IPS\forums\Topic\Post::loadAndCheckPerms( \IPS\Request::i()->answer );
if ( $post->item() != $topic )
{
throw new \OutOfRangeException;
@@ -738,31 +811,23 @@ class _topic extends \IPS\Content\Controller
{
\IPS\Output::i()->error( 'node_error', '2F173/7', 404, '' );
}
if ( $topic->topic_answered_pid )
$topic->toggleSolveComment( $post->pid, TRUE );
if( $post->author()->member_id != \IPS\Member::loggedIn()->member_id )
{
try
{
$oldBestAnswer = \IPS\forums\Topic\Post::load( $topic->topic_answered_pid );
$oldBestAnswer->post_bwoptions['best_answer'] = FALSE;
$oldBestAnswer->save();
}
catch ( \Exception $e ) {}
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'best_answer', $topic, array( $post ), array( $post->author()->member_id ) );
$notification->recipients->attach( $post->author() );
$notification->send();
}
$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() );
\IPS\Output::i()->redirect( $post->url() );
}
/**
@@ -777,6 +842,8 @@ class _topic extends \IPS\Content\Controller
try
{
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
$post = \IPS\forums\Topic\Post::loadAndCheckPerms( \IPS\Request::i()->answer );
if ( !$topic->canSetBestAnswer() )
{
throw new \OutOfRangeException;
@@ -786,14 +853,17 @@ class _topic extends \IPS\Content\Controller
{
\IPS\Output::i()->error( 'node_error', '2F173/G', 404, '' );
}
if ( $post->item() != $topic )
{
throw new \OutOfRangeException;
}
if ( $topic->topic_answered_pid )
{
try
{
$post = \IPS\forums\Topic\Post::load( $topic->topic_answered_pid );
$post->post_bwoptions['best_answer'] = FALSE;
$post->save();
$topic->toggleSolveComment( $post->pid, FALSE );
if ( \IPS\Member::loggedIn()->modPermission('can_set_best_answer') )
{
@@ -803,10 +873,7 @@ class _topic extends \IPS\Content\Controller
catch ( \Exception $e ) {}
}
$topic->topic_answered_pid = FALSE;
$topic->save();
\IPS\Output::i()->redirect( $topic->url() );
\IPS\Output::i()->redirect( $post->url() );
}
/**
@@ -885,7 +952,47 @@ class _topic extends \IPS\Content\Controller
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'widgets', 'forums', 'front' )->poll( $topic, $poll );
}
/**
* Show a single comment requested by ajax
*
* @return void
*/
public function ajaxShowComment()
{
try
{
if ( ! \IPS\Request::i()->isAjax() )
{
throw new \BadMethodCallException();
}
\IPS\Session::i()->csrfCheck();
try
{
$topic = \IPS\forums\Topic::loadAndCheckPerms( \IPS\Request::i()->id );
}
catch( \OutOfRangeException $ex )
{
\IPS\Output::i()->error( 'node_error', '2F173/N', 403, '' );
}
$comment = \IPS\forums\Topic\Post::load( \IPS\Request::i()->showComment );
if ( ! $comment->canView() )
{
throw new \BadMethodCallException();
}
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( \IPS\Theme::i()->getTemplate( 'global', 'core' )->commentContainer( $topic, $comment ), 200, 'text/html' ) );
}
catch( \Exception $e )
{
return '';
}
}
/**
* Find a Comment / Review (do=findComment/findReview)
*
@@ -949,7 +1056,7 @@ class _topic extends \IPS\Content\Controller
$page = ceil( $commentPosition / $perPage );
if ( $page != 1 )
{
$url = $url->setQueryString( 'page', $page );
$url = $url->setPage( 'page', $page );
}
/* And redirect */
@@ -1042,4 +1149,20 @@ class _topic extends \IPS\Content\Controller
return $form;
}
/**
* Split Comment
*
* @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 _split( $commentClass, $comment, $item )
{
parent::_split( $commentClass, $comment, $item );
$item->rebuildPopularTime();
}
}