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

-408
View File
@@ -1,408 +0,0 @@
<?php
/**
* @brief Feed Import Node
* @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 4 Fed 2014
*/
namespace IPS\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;
}
/**
* Feed Import Node
*/
class _Feed extends \IPS\Node\Model
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'forums_rss_import';
/**
* @brief Database Prefix
*/
public static $databasePrefix = 'rss_import_';
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'rss_import';
/**
* @brief [Node] ACP Restrictions
* @code
array(
'app' => 'core', // The application key which holds the restrictrions
'module' => 'foo', // The module key which holds the restrictions
'map' => array( // [Optional] The key for each restriction - can alternatively use "prefix"
'add' => 'foo_add',
'edit' => 'foo_edit',
'permissions' => 'foo_perms',
'delete' => 'foo_delete'
),
'all' => 'foo_manage', // [Optional] The key to use for any restriction not provided in the map (only needed if not providing all 4)
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
* @endcode
*/
protected static $restrictions = array(
'app' => 'forums',
'module' => 'forums',
'prefix' => 'rss_'
);
/**
* [Node] Get title
*
* @return string
*/
protected function get__title()
{
return $this->title;
}
/**
* [Node] Get description
*
* @return string
*/
protected function get__description()
{
return $this->url;
}
/**
* [Node] Get whether or not this node is enabled
*
* @note Return value NULL indicates the node cannot be enabled/disabled
* @return bool|null
*/
protected function get__enabled()
{
return $this->enabled;
}
/**
* [Node] Set whether or not this node is enabled
*
* @param bool|int $enabled Whether to set it enabled or disabled
* @return void
*/
protected function set__enabled( $enabled )
{
if ( $enabled )
{
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 'rssimport' ) );
}
$this->enabled = $enabled;
}
/**
* [Node] Get buttons to display in tree
* Example code explains return value
*
* @code
array(
array(
'icon' => 'plus-circle', // Name of FontAwesome icon to use
'title' => 'foo', // Language key to use for button's title parameter
'link' => \IPS\Http\Url::internal( 'app=foo...' ) // URI to link to
'class' => 'modalLink' // CSS Class to use on link (Optional)
),
... // Additional buttons
);
* @endcode
* @param string $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
*/
public function getButtons( $url, $subnode=FALSE )
{
$buttons = parent::getButtons( $url, $subnode );
if ( \IPS\Member::loggedIn()->hasAcpRestriction('rss_run') )
{
$buttons = array_merge( array( 'run' => array(
'icon' => 'refresh',
'title' => 'rss_run',
'link' => $url->setQueryString( array( 'do' => 'run', 'id' => $this->_id ) )
) ), $buttons );
}
return $buttons;
}
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @return void
*/
public function form( &$form )
{
$form->addHeader('rss_import_url');
$form->add( new \IPS\Helpers\Form\Url( 'rss_import_url', $this->url, TRUE ) );
$form->add( new \IPS\Helpers\Form\Text( 'rss_import_auth_user', $this->auth_user ) );
$form->add( new \IPS\Helpers\Form\Password( 'rss_import_auth_pass', $this->auth_pass ) );
$form->addHeader('rss_import_details');
$form->add( new \IPS\Helpers\Form\Node( 'rss_import_forum_id', $this->forum_id, TRUE, array( 'class' => 'IPS\forums\Forum', 'permissionCheck' => function ( $forum )
{
return $forum->sub_can_post and !$forum->redirect_url;
} ) ) );
$form->add( new \IPS\Helpers\Form\Member( 'rss_import_mid', \IPS\Member::load( $this->mid ), TRUE ) );
$form->add( new \IPS\Helpers\Form\Text( 'rss_import_showlink', $this->showlink ) );
$form->add( new \IPS\Helpers\Form\Radio( 'rss_import_topic_open', $this->topic_open, FALSE, array( 'options' => array( 1 => 'unlocked', 0 => 'locked' ) ) ) );
$form->add( new \IPS\Helpers\Form\Radio( 'rss_import_topic_hide', $this->topic_hide, FALSE, array( 'options' => array( 0 => 'unhidden', 1 => 'hidden' ) ) ) );
$form->add( new \IPS\Helpers\Form\Text( 'rss_import_topic_pre', $this->topic_pre, FALSE, array( 'trim' => FALSE ) ) );
}
/**
* [Node] Format form values from add/edit form for save
*
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
{
if( isset( $values['rss_import_url'] ) )
{
try
{
$request = $values['rss_import_url']->request();
if ( $values['rss_import_auth_user'] or $values['rss_import_auth_pass'] )
{
$request = $request->login( $values['rss_import_auth_user'], $values['rss_import_auth_pass'] );
}
$response = $request->get();
if ( $response->httpResponseCode == 401 )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_auth' ) );
}
$response = $response->decodeXml();
if ( !( $response instanceof \IPS\Xml\Rss ) and !( $response instanceof \IPS\Xml\Atom ) )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_invalid' ) );
}
}
catch ( \IPS\Http\Request\Exception $e )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_url_bad' ) );
}
catch( \RuntimeException $e )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_invalid' ) );
}
catch ( \ErrorException $e )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_invalid' ) );
}
$values['title'] = (string) $request->get()->decodeXml()->channel->title;
}
if( isset( $values['rss_import_forum_id'] ) )
{
$values['forum_id'] = $values['rss_import_forum_id']->_id;
unset( $values['rss_import_forum_id'] );
}
if( isset( $values['rss_import_mid'] ) )
{
$values['mid'] = $values['rss_import_mid']->member_id;
unset( $values['rss_import_mid'] );
}
if( isset( $values['rss_import_auth_user'] ) OR isset( $values['rss_import_auth_pass'] ) )
{
$values['auth'] = ( $values['rss_import_auth_user'] or $values['rss_import_auth_pass'] );
}
return $values;
}
/**
* [Node] Perform actions after saving the form
*
* @param array $values Values from the form
* @return void
*/
public function postSaveForm( $values )
{
$this->run();
}
/**
* Run Import
*
* @return void
* @throws \IPS\Http\Url\Exception
*/
public function run()
{
/* Skip this if the member is restricted from posting */
if( \IPS\Member::load( $this->mid )->restrict_post or \IPS\Member::load( $this->mid )->members_bitoptions['unacknowledged_warnings'] )
{
return;
}
$previouslyImportedGuids = iterator_to_array( \IPS\Db::i()->select( 'rss_imported_guid', 'forums_rss_imported', array( 'rss_imported_impid=?', $this->id ) ) );
$request = \IPS\Http\Url::external( $this->url )->request();
if ( $this->auth )
{
$request = $request->login( $this->auth_user, $this->auth_pass );
}
$request = $request->get();
$container = \IPS\forums\Forum::load( $this->forum_id );
$i = 0;
$inserts = array();
$request = $request->decodeXml();
if( !( $request instanceof \IPS\Xml\Rss1 ) AND !( $request instanceof \IPS\Xml\Rss ) AND !( $request instanceof \IPS\Xml\Atom ) )
{
throw new \RuntimeException( 'rss_import_invalid' );
}
$post = NULL;
foreach ( $request->articles( $this->id ) as $guid => $article )
{
if ( !in_array( $guid, $previouslyImportedGuids ) )
{
/* Don't post future date content - this breaks things like marking as read */
if( $article['date']->getTimestamp() > time() )
{
$artice['date'] = \IPS\DateTime::create();
}
$topic = \IPS\forums\Topic::createItem( \IPS\Member::load( $this->mid ), NULL, $article['date'], $container, $this->topic_hide );
$topic->title = $this->topic_pre . $article['title'];
if ( !$this->topic_open )
{
$topic->state = 'closed';
}
$topic->save();
/* Send notifications */
if ( !$this->topic_hide )
{
$topic->sendNotifications();
}
/* Add to search index */
\IPS\Content\Search\Index::i()->index( $topic );
$readMoreLink = '';
if ( $article['link'] and $this->showlink )
{
$rel = array();
if( \IPS\Settings::i()->posts_add_nofollow )
{
$rel['nofollow'] = 'nofollow';
}
if( \IPS\Settings::i()->links_external )
{
$rel['external'] = 'external';
}
$linkRelPart = '';
if ( count ( $rel ) )
{
$linkRelPart = 'rel="' . implode($rel, ' ') . '"';
}
$readMoreLink = "<p><a href='{$article['link']}' {$linkRelPart}>{$this->showlink}</a></p>";
}
$member = \IPS\Member::load( $this->mid );
$content = \IPS\Text\Parser::parseStatic( $article['content'] . $readMoreLink, TRUE, NULL, $member, 'forums_Forums', TRUE, !(bool) $member->group['g_dohtml'] );
$post = \IPS\forums\Topic\Post::create( $topic, $content, TRUE, NULL, \IPS\forums\Topic\Post::incrementPostCount( $container ), $member, $article['date'] );
$topic->topic_firstpost = $post->pid;
$topic->save();
$inserts[] = array(
'rss_imported_guid' => $guid,
'rss_imported_tid' => $topic->tid,
'rss_imported_impid'=> $this->id
);
$i++;
if ( $i >= 10 )
{
break;
}
}
}
if( count( $inserts ) )
{
\IPS\Db::i()->insert( 'forums_rss_imported', $inserts );
}
$this->last_import = time();
$this->save();
if ( $post !== NULL )
{
$container->setLastComment( $post );
$container->save();
}
}
/**
* [ActiveRecord] Delete Record
*
* @return void
*/
public function delete()
{
\IPS\Db::i()->delete( 'forums_rss_imported', array( "rss_imported_impid=?", $this->id ) );
return parent::delete();
}
/**
* Search
*
* @param string $column Column to search
* @param string $query Search query
* @param string|null $order Column to order by
* @param mixed $where Where clause
* @return array
*/
public static function search( $column, $query, $order=NULL, $where=array() )
{
if ( $column === '_title' )
{
$column = 'rss_import_title';
}
if ( $order === '_title' )
{
$order = 'rss_import_title';
}
return parent::search( $column, $query, $order, $where );
}
}
Whitespace-only changes.
+253 -142
View File
@@ -12,7 +12,7 @@
namespace IPS\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;
@@ -112,10 +112,11 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
public static $bitOptions = array(
'forums_bitoptions' => array(
'forums_bitoptions' => array(
'bw_disable_tagging' => 1,
'bw_disable_prefixes' => 2,
'bw_enable_answers' => 4,
'bw_enable_answers_member' => 8,
'bw_disable_tagging' => 1,
'bw_disable_prefixes' => 2,
'bw_enable_answers' => 4,
'bw_enable_answers_member' => 8,
'bw_enable_answers_moderator' => 16
)
)
);
@@ -381,7 +382,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function getLastCommentTime()
{
if( !$this->can_view_others and !\IPS\Member::loggedIn()->modPermission( 'can_read_all_topics' ) )
if( !$this->memberCanAccessOthersTopics( \IPS\Member::loggedIn() ) )
{
try
{
@@ -398,23 +399,6 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return $this->last_post ? \IPS\DateTime::ts( $this->last_post ) : NULL;
}
/**
* Set the comment/approved/hidden counts
*
* @return void
*/
public function resetCommentCounts()
{
parent::resetCommentCounts();
/* Archiving? */
try
{
$this->_comments += \IPS\forums\Topic\ArchivedPost::db()->select( 'COUNT(*)', 'forums_archive_posts', array( 'archive_forum_id=?', $this->id ) )->first();
}
catch( \Exception $e ){}
}
/**
* Get last post data
*
@@ -422,41 +406,11 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function lastPost()
{
/* If this is a forum where users cannot see other user's posts, and the user is not a moderator
who can override that, we'll show the last post that was created by the user */
$fetchLastPostFromUser = FALSE;
/* This forum does not allow you to see topics from other users... */
if( !$this->can_view_others )
{
/* If we do not have the 'can_read_all_topics' permission, then it doesn't matter what forums we can moderate - we can't see
other users posts */
if( !\IPS\Member::loggedIn()->modPermission( 'can_read_all_topics' ) )
{
$fetchLastPostFromUser = TRUE;
}
/* If we hit the else statement, we do have moderator permissions to read all topics, so now we have to see if we can
moderate in this forum */
else
{
/* If we are not a moderator of all forums... */
if( \IPS\Member::loggedIn()->modPermission( 'forums' ) !== -1 AND \IPS\Member::loggedIn()->modPermission( 'forums' ) !== TRUE )
{
/* If we cannot moderate in this forum, we need to fetch our last post */
if( !is_array( \IPS\Member::loggedIn()->modPermission( 'forums' ) ) OR
!in_array( $this->_id, \IPS\Member::loggedIn()->modPermission( 'forums' ) ) )
{
$fetchLastPostFromUser = TRUE;
}
}
}
}
if ( !$this->loggedInMemberHasPasswordAccess() )
{
return NULL;
}
elseif ( $fetchLastPostFromUser )
elseif ( !$this->memberCanAccessOthersTopics( \IPS\Member::loggedIn() ) )
{
try
{
@@ -541,7 +495,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
}
}
if ( $this->sub_can_post and !$this->permission_showtopic and !$this->can('read') AND !is_null( $result ) )
if ( $this->sub_can_post and !$this->permission_showtopic and !$this->can('read') AND !\is_null( $result ) )
{
$result['topic_title'] = NULL;
}
@@ -689,6 +643,27 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Settings::i()->viglink_enabled = FALSE;
}
}
/**
* Fetch All Root Nodes
*
* @param string|NULL $permissionCheck The permission key to check for or NULl to not check permissions
* @param \IPS\Member|NULL $member The member to check permissions for or NULL for the currently logged in member
* @param mixed $where Additional WHERE clause
* @param array|NULL $limit Limit/offset to use, or NULL for no limit (default)
* @return array
*/
public static function roots( $permissionCheck='view', $member=NULL, $where=array(), $limit=NULL )
{
/* @todo remove this temp fix */
if( !isset( \IPS\Data\Store::i()->_forumsChecked ) OR !\IPS\Data\Store::i()->_forumsChecked )
{
\IPS\Db::i()->update( 'forums_forums', array( 'parent_id' => '-1' ), array( 'parent_id=?', 0 ) );
\IPS\Data\Store::i()->_forumsChecked = 1;
}
return parent::roots( $permissionCheck, $member, $where, $limit );
}
/**
* Load into memory (taking permissions into account)
@@ -700,9 +675,16 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public static function loadIntoMemory( $permissionCheck='view', $member=NULL, $where = array() )
{
/* @todo remove this temp fix */
if( !isset( \IPS\Data\Store::i()->_forumsChecked ) OR !\IPS\Data\Store::i()->_forumsChecked )
{
\IPS\Db::i()->update( 'forums_forums', array( 'parent_id' => '-1' ), array( 'parent_id=?', 0 ) );
\IPS\Data\Store::i()->_forumsChecked = 1;
}
$member = $member ?: \IPS\Member::loggedIn();
if ( in_array( $permissionCheck, array( 'add', 'reply' ) ) )
if ( \in_array( $permissionCheck, array( 'add', 'reply' ) ) )
{
$where[] = array( 'sub_can_post=1' );
$where[] = array( 'min_posts_post<=?', $member->member_posts );
@@ -710,18 +692,14 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( $permissionCheck == 'view' )
{
$where[] = array( 'min_posts_view<=?', $member->member_posts );
if ( !$member->member_id )
{
$where[] = array( '(sub_can_post=0 OR can_view_others=1)' );
}
$where[] = array( '(sub_can_post=0 OR min_posts_view<=?)', $member->member_posts );
}
if ( in_array( $permissionCheck, array( 'read', 'add' ) ) )
if ( \in_array( $permissionCheck, array( 'read', 'add' ) ) )
{
$whereString = 'password=? OR ' . \IPS\Db::i()->findInSet( 'forums_forums.password_override', $member->groups );
$whereParams = array( NULL );
if ( $member === \IPS\Member::loggedIn() )
if ( $member->member_id === \IPS\Member::loggedIn()->member_id )
{
foreach ( \IPS\Request::i()->cookie as $k => $v )
{
@@ -742,35 +720,32 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
/**
* Check permissions
*
* @param mixed $permission A key which has a value in static::$permissionMap['view'] matching a column ID in core_permission_index
* @param \IPS\Member|\IPS\Member\Group|NULL $member The member or group to check (NULL for currently logged in member)
* @param mixed $permission A key which has a value in static::$permissionMap['view'] matching a column ID in core_permission_index
* @param \IPS\Member|\IPS\Member\Group|NULL $member The member or group to check (NULL for currently logged in member)
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will return TRUE if "Post Before Registering" feature is enabled
* @return bool
* @throws \OutOfBoundsException If $permission does not exist in static::$permissionMap
*/
public function can( $permission, $member=NULL )
public function can( $permission, $member=NULL, $considerPostBeforeRegistering = TRUE )
{
if ( !$this->sub_can_post and in_array( $permission, array( 'add', 'reply' ) ) )
if ( !$this->sub_can_post and \in_array( $permission, array( 'add', 'reply' ) ) )
{
return FALSE;
}
$_member = $member ?: \IPS\Member::loggedIn();
if ( $permission == 'view' and $this->min_posts_view and $this->min_posts_view > $_member->member_posts )
if ( $permission == 'view' and $this->sub_can_post and $this->min_posts_view and $this->min_posts_view > $_member->member_posts )
{
return FALSE;
}
if ( !$_member->member_id and $this->sub_can_post and !$this->can_view_others )
{
return FALSE;
}
if ( in_array( $permission, array( 'add', 'reply' ) ) and $this->min_posts_post and $this->min_posts_post > $_member->member_posts )
if ( \in_array( $permission, array( 'add', 'reply' ) ) and $this->min_posts_post and $this->min_posts_post > $_member->member_posts )
{
return FALSE;
}
$return = parent::can( $permission, $member );
$return = parent::can( $permission, $member, $considerPostBeforeRegistering );
if ( $return === TRUE and $this->password !== NULL and in_array( $permission, array( 'read', 'add' ) ) and ( ( $member !== NULL and $member !== \IPS\Member::loggedIn() ) or !$this->loggedInMemberHasPasswordAccess() ) )
if ( $return === TRUE and $this->password !== NULL and \in_array( $permission, array( 'read', 'add' ) ) and ( ( $member !== NULL and $member->member_id !== \IPS\Member::loggedIn()->member_id ) or !$this->loggedInMemberHasPasswordAccess() ) )
{
return FALSE;
}
@@ -790,7 +765,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$message = trim( \IPS\Member::loggedIn()->language()->get( "forums_forum_{$this->id}_permerror" ) );
if ( $message and $message != '<p></p>' )
{
return $message;
return \IPS\Theme::i()->getTemplate('global', 'core', 'global')->richText( $message, array('ipsType_normal') );
}
}
@@ -892,7 +867,9 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
"form_{$id}_header_forum_display_topic",
'forum_preview_posts',
'forum_icon',
'forum_sort_key'
'forum_sort_key',
'forum_best_answer',
'forum_use_feature_color'
),
'qa' => array(
'forum_password_on',
@@ -914,7 +891,8 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
'forum_qa_rate_answers',
'forum_preview_posts_qa',
'forum_icon',
'forum_sort_key_qa'
'forum_sort_key_qa',
'forum_use_feature_color'
),
'category' => array(
"form_{$id}_tab_forum_display",
@@ -924,18 +902,20 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
'redirect' => array(
'forum_password_on',
'forum_redirect_url',
'forum_redirect_hits'
'forum_redirect_hits',
'forum_icon',
'forum_use_feature_color'
),
)
) ) );
$class = get_called_class();
$class = \get_called_class();
$form->add( new \IPS\Helpers\Form\Node( 'forum_parent_id', ( !$this->id AND $this->parent_id === -1 ) ? NULL : ( $this->parent_id === -1 ? 0 : $this->parent_id ), FALSE, array(
'class' => '\IPS\forums\Forum',
'disabled' => array(),
'zeroVal' => 'node_no_parentf',
'zeroValTogglesOff' => array( 'form_new_forum_type', 'forum_icon' ),
'zeroValTogglesOff' => array( 'form_new_forum_type', 'forum_icon', 'forum_card_image' ),
'permissionCheck' => function( $node ) use ( $class )
{
if( isset( $class::$subnodeClass ) AND $class::$subnodeClass AND $node instanceof $class::$subnodeClass )
@@ -952,15 +932,21 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
throw new \DomainException('forum_parent_id_error');
}
} ) );
/* Color */
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_use_feature_color', $this->feature_color ? 1 : 0, FALSE, array( 'togglesOn' => array( 'forum_feature_color' ) ), NULL, NULL, NULL, 'forum_use_feature_color' ) );
$form->add( new \IPS\Helpers\Form\Color( 'forum_feature_color', $this->feature_color ?: '', FALSE, array(), NULL, NULL, NULL, 'forum_feature_color' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'forum_icon', $this->icon ? \IPS\File::get( 'forums_Icons', $this->icon ) : NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'forums_Icons' ), NULL, NULL, NULL, 'forum_icon' ) );
$form->add( new \IPS\Helpers\Form\Upload( 'forum_card_image', $this->card_image ? \IPS\File::get( 'forums_Cards', $this->card_image ) : NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'forums_Cards', 'allowStockPhotos' => TRUE ), NULL, NULL, NULL, 'forum_card_image' ) );
$form->add( new \IPS\Helpers\Form\Url( 'forum_redirect_url', $this->id ? $this->redirect_url : array(), FALSE, array( 'placeholder' => 'http://www.example.com/' ), NULL, NULL, NULL, 'forum_redirect_url' ) );
$form->add( new \IPS\Helpers\Form\Number( 'forum_redirect_hits', $this->id ? $this->redirect_hits : 0, FALSE, array(), NULL, NULL, NULL, 'forum_redirect_hits' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_password_on', $this->id ? ( $this->password !== NULL ) : FALSE, FALSE, array( 'togglesOn' => array( 'forum_password', 'forum_password_override' ) ), NULL, NULL, NULL, 'forum_password_on' ) );
$form->add( new \IPS\Helpers\Form\Password( 'forum_password', $this->password, FALSE, array(), NULL, NULL, NULL, 'forum_password' ) );
$form->add( new \IPS\Helpers\Form\Select( 'forum_password_override', $this->id ? explode( ',', $this->password_override ) : array(), FALSE, array( 'options' => $groups, 'multiple' => TRUE ), NULL, NULL, NULL, 'forum_password_override' ) );
if ( count( \IPS\Theme::themes() ) > 1 )
if ( \count( \IPS\Theme::themes() ) > 1 )
{
$themes = array( 0 => 'forum_skin_id_default' );
foreach ( \IPS\Theme::themes() as $theme )
@@ -1029,10 +1015,6 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$form->add( new \IPS\Helpers\Form\Translatable( 'forum_rules_title', NULL, FALSE, array( 'app' => 'forums', 'key' => ( $this->id ? "forums_forum_{$this->id}_rulestitle" : NULL ) ), NULL, NULL, NULL, 'forum_rules_title' ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'forum_rules_text', NULL, FALSE, array( 'app' => 'forums', 'key' => ( $this->id ? "forums_forum_{$this->id}_rules" : NULL ), 'editor' => array( 'app' => 'forums', 'key' => 'Forums', 'autoSaveKey' => ( $this->id ? "forums-rules-{$this->id}" : "forums-new-rules" ), 'attachIds' => $this->id ? array( $this->id, NULL, 'rules' ) : NULL ) ), NULL, NULL, NULL, 'forum_rules_text' ) );
/* Color */
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_use_feature_color', $this->feature_color ? 1 : 0, FALSE, array( 'togglesOn' => array( 'forum_feature_color' ) ), NULL, NULL, NULL, 'forum_use_feature_color' ) );
$form->add( new \IPS\Helpers\Form\Color( 'forum_feature_color', $this->feature_color ?: '', FALSE, array(), NULL, NULL, NULL, 'forum_feature_color' ) );
$form->addHeader( 'forum_display_topic' );
$form->addHeader( 'forum_display_question' );
$form->add( new \IPS\Helpers\Form\YesNo( 'bw_enable_answers_member', $this->id ? $this->forums_bitoptions['bw_enable_answers_member'] : TRUE, FALSE, array(), NULL, NULL, NULL, 'bw_enable_answers_member' ) );
@@ -1040,7 +1022,8 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$form->add( new \IPS\Helpers\Form\Select( 'forum_qa_rate_answers', $this->id ? ( ( $this->qa_rate_answers == '*' or $this->qa_rate_answers === NULL ) ? '*' : explode( ',', $this->qa_rate_answers ) ) : '*', FALSE, array( 'options' => $groupsNoGuests, 'unlimited' => '*', 'multiple' => TRUE ), NULL, NULL, NULL, 'forum_qa_rate_answers' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_forum_allow_rating', $this->id ? $this->forum_allow_rating : FALSE, FALSE, array(), NULL, NULL, NULL, 'forum_allow_rating' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_disable_sharelinks', $this->id ? !$this->disable_sharelinks : TRUE, FALSE, array(), NULL, NULL, NULL, 'forum_disable_sharelinks' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'bw_enable_answers_moderator', $this->id ? $this->forums_bitoptions['bw_enable_answers_moderator'] : FALSE, FALSE, array( 'togglesOn' => array( 'bw_enable_answers__member' ) ), NULL, NULL, NULL, 'forum_best_answer' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'bw_enable_answers__member', $this->id ? $this->forums_bitoptions['bw_enable_answers_member'] : FALSE, FALSE, array(), NULL, NULL, NULL, 'bw_enable_answers__member' ) );
$form->addTab( 'posting_settings' );
$form->addHeader('posts');
@@ -1075,10 +1058,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$form->addHeader( 'tags' );
$form->add( new \IPS\Helpers\Form\YesNo( 'bw_disable_tagging', !$this->forums_bitoptions['bw_disable_tagging'], FALSE, array( 'togglesOn' => array( 'bw_disable_prefixes', 'forum_tag_predefined' ) ), NULL, NULL, NULL, 'bw_disable_tagging' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'bw_disable_prefixes', !$this->forums_bitoptions['bw_disable_prefixes'], FALSE, array(), NULL, NULL, NULL, 'bw_disable_prefixes' ) );
if ( !\IPS\Settings::i()->tags_open_system )
{
$form->add( new \IPS\Helpers\Form\Text( 'forum_tag_predefined', $this->tag_predefined ?: NULL, FALSE, array( 'autocomplete' => array( 'unique' => 'true' ), 'nullLang' => 'forum_tag_predefined_unlimited' ), NULL, NULL, NULL, 'forum_tag_predefined' ) );
}
$form->add( new \IPS\Helpers\Form\Text( 'forum_tag_predefined', $this->tag_predefined ?: NULL, FALSE, array( 'autocomplete' => array( 'unique' => 'true' ), 'nullLang' => 'forum_tag_predefined_unlimited' ), NULL, NULL, NULL, 'forum_tag_predefined' ) );
}
}
@@ -1129,6 +1109,11 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
else
{
$values['forums_bitoptions']['bw_enable_answers'] = FALSE;
if ( isset( $values['bw_enable_answers__member'] ) )
{
$values['bw_enable_answers_member'] = $values['bw_enable_answers__member'];
}
}
}
else
@@ -1137,17 +1122,34 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$values['redirect_on'] = TRUE;
}
if ( isset( $values['bw_enable_answers__member'] ) )
{
unset( $values['bw_enable_answers__member'] );
}
unset( $values['forum_can_view_others_qa'] );
unset( $values['forum_permission_showtopic_qa'] );
unset( $values['forum_preview_posts_qa'] );
unset( $values['forum_sort_key_qa'] );
}
/* We are copying the "Allow user to mark topic as solved" option */
if( !isset( $values['forum_type'] ) AND isset( $values['bw_enable_answers__member'] ) )
{
/* We don't want to copy this to QA forums */
if( $this->forum_type == 'normal' )
{
$values['bw_enable_answers_member'] = $values['bw_enable_answers__member'];
}
unset( $values['bw_enable_answers__member'] );
}
if ( isset( $values['forum_parent_id'] ) )
{
if ( $values['forum_parent_id'] )
{
$values['forum_parent_id'] = is_scalar( $values['forum_parent_id'] ) ? intval( $values['forum_parent_id'] ) : intval( $values['forum_parent_id']->id );
$values['forum_parent_id'] = is_scalar( $values['forum_parent_id'] ) ? \intval( $values['forum_parent_id'] ) : \intval( $values['forum_parent_id']->id );
}
else
{
@@ -1156,11 +1158,11 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
}
/* Bitwise */
foreach ( array( 'bw_disable_tagging', 'bw_disable_prefixes', 'bw_enable_answers_member' ) as $k )
foreach ( array( 'bw_disable_tagging', 'bw_disable_prefixes', 'bw_enable_answers_member', 'bw_enable_answers_moderator' ) as $k )
{
if( isset( $values[ $k ] ) )
{
$values['forums_bitoptions'][ $k ] = ( $k == 'bw_enable_answers_member' ) ? $values[ $k ] : !$values[ $k ];
$values['forums_bitoptions'][ $k ] = ( $k == 'bw_enable_answers_member' or $k == 'bw_enable_answers_moderator' ) ? $values[ $k ] : !$values[ $k ];
unset( $values[ $k ] );
}
}
@@ -1185,7 +1187,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if ( isset( $values[ $k ] ) )
{
$values[ $k ] = ( is_array( $values[ $k ] ) ) ? implode( ',', $values[ $k ] ) : $values[ $k ];
$values[ $k ] = ( \is_array( $values[ $k ] ) ) ? implode( ',', $values[ $k ] ) : $values[ $k ];
}
}
@@ -1196,7 +1198,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
}
/* Reset password and can view others if toggling back to a category */
if( in_array( $values['type'], array( 'category', 'redirect' ) ) )
if( isset( $values['type'] ) AND \in_array( $values['type'], array( 'category', 'redirect' ) ) )
{
$values['password'] = NULL;
$values['can_view_others'] = TRUE;
@@ -1211,15 +1213,15 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
/* Moderation */
if( isset( $values['preview_posts'] ) )
{
if ( in_array( 'topics', $values['preview_posts'] ) and in_array( 'posts', $values['preview_posts'] ) )
if ( \in_array( 'topics', $values['preview_posts'] ) and \in_array( 'posts', $values['preview_posts'] ) )
{
$values['preview_posts'] = 1;
}
elseif ( in_array( 'topics', $values['preview_posts'] ) )
elseif ( \in_array( 'topics', $values['preview_posts'] ) )
{
$values['preview_posts'] = 2;
}
elseif ( in_array( 'posts', $values['preview_posts'] ) )
elseif ( \in_array( 'posts', $values['preview_posts'] ) )
{
$values['preview_posts'] = 3;
}
@@ -1271,7 +1273,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
}
/* Update index */
if( $this->can_view_others !== NULL and isset( $values['can_view_others'] ) and $values['can_view_others'] != $this->can_view_others )
if( $this->can_view_others !== NULL and array_key_exists( 'can_view_others', $values ) and $values['can_view_others'] != $this->can_view_others )
{
$this->can_view_others = $values['can_view_others'];
$this->updateSearchIndexPermissions();
@@ -1296,6 +1298,8 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
/**
* Can a value be copied to this node?
*
* @param string $key Setting key
* @param mixed $value Setting value
* @return bool
*/
public function canCopyValue( $key, $value )
@@ -1334,8 +1338,6 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function delete()
{
\IPS\File::unclaimAttachments( 'forums_Forums', $this->id );
try
{
\IPS\File::get( 'forums_Icons', $this->icon )->delete();
@@ -1421,6 +1423,51 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
return static::theOnlyNode( array( 'redirect_url' => FALSE, 'password' => FALSE ), FALSE );
}
/**
* Can a given member view topics created by other members in this forum?
*
* @param \IPS\Member $member The member
* @return bool
*/
public function memberCanAccessOthersTopics( \IPS\Member $member )
{
/* If everyone can view topics posted in this forum by others then this whole check is irrelevant */
if ( $this->can_view_others )
{
return TRUE;
}
/* If this is a club forum, we defer to if the member is a moderator of it (which will include members who have the global "Has leader privileges in all clubs?" permission") */
elseif ( $club = $this->club() )
{
return $club->isModerator( $member );
}
/* Otherwise, it depends on if this user is a moderator with the "Can view all topics/questions?" permission for THIS forum */
else
{
if ( $member->modPermission('can_read_all_topics') )
{
$forumsTheMemberIsModeratorOf = $member->modPermission('forums');
if ( $forumsTheMemberIsModeratorOf === TRUE ) // All forums
{
return TRUE;
}
elseif ( \is_array( $forumsTheMemberIsModeratorOf ) and \in_array( $this->_id, $forumsTheMemberIsModeratorOf ) ) // This forum specifically
{
return TRUE;
}
else
{
return FALSE;
}
}
else
{
return FALSE;
}
}
}
/**
* Get which permission keys can access all topics in a forum which
@@ -1434,28 +1481,36 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$return = array();
$members = array();
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $moderator )
if ( $club = $this->club() )
{
if ( $moderator['perms'] === '*' or in_array( 'can_read_all_topics', explode( ',', $moderator['perms'] ) ) )
$return[] = 'cm';
$members[] = "cm{$club->id}";
}
else
{
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $moderator )
{
if( $moderator['type'] === 'g' )
if ( $moderator['perms'] === '*' or \in_array( 'can_read_all_topics', explode( ',', $moderator['perms'] ) ) )
{
$return[] = $moderator['id'];
}
else
{
$members[] = "m{$moderator['id']}";
if( $moderator['type'] === 'g' )
{
$return[] = $moderator['id'];
}
else
{
$members[] = "m{$moderator['id']}";
}
}
}
}
$return = ( $normal == '*' ) ? array_unique( $return ) : array_intersect( explode( ',', $normal ), array_unique( $return ) );
if( count( $members ) )
if( \count( $members ) )
{
$return = array_merge( $return, $members );
}
return $return;
}
@@ -1498,7 +1553,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
if( isset( $data['additional'] ) AND
( isset( $data['additional']['author'] ) OR ( isset( $data['additional']['no_comments'] ) AND $data['additional']['no_comments'] > 0 ) OR
( isset( $data['additional']['num_comments'] ) AND $data['additional']['num_comments'] > 0 ) OR isset( $data['additional']['state'] ) OR
( isset( $data['additional']['pinned'] ) AND $data['additional']['pinned'] === TRUE ) OR ( isset( $data['additional']['featured'] ) AND $data['additional']['featured'] === TRUE ) )
( isset( $data['additional']['pinned'] ) AND $data['additional']['pinned'] === TRUE ) OR ( isset( $data['additional']['featured'] ) AND $data['additional']['featured'] === TRUE ) OR ( isset( $data['additional']['last_post'] ) AND $data['additional']['last_post'] > 0 ) OR ( isset( $data['additional']['date'] ) AND $data['additional']['date'] > 0 ) )
)
{
return parent::massMoveorDelete( $node, $data );
@@ -1559,7 +1614,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Db::i()->update( 'forums_question_ratings', array( 'forum' => $node->_id ), array( 'forum=?', $this->_id ) );
/* Rebuild tags */
if ( in_array( 'IPS\Content\Tags', class_implements( $contentItemClass ) ) )
if ( \in_array( 'IPS\Content\Tags', class_implements( $contentItemClass ) ) )
{
\IPS\Db::i()->update( 'core_tags', array(
'tag_aap_lookup' => md5( static::$permApp . ';' . static::$permType . ';' . $node->_id ),
@@ -1584,7 +1639,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$this->save();
/* Add to search index */
if ( in_array( 'IPS\Content\Searchable', class_implements( $contentItemClass ) ) )
if ( \in_array( 'IPS\Content\Searchable', class_implements( $contentItemClass ) ) )
{
/* Grab permissions...we already account for !can_view_others by letting the parent handle this the old fashioned way in that case at the start of the method */
$permissions = $node->searchIndexPermissions();
@@ -1597,7 +1652,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( isset( $contentItemClass::$$class ) )
{
$className = $contentItemClass::$$class;
if ( in_array( 'IPS\Content\Searchable', class_implements( $className ) ) )
if ( \in_array( 'IPS\Content\Searchable', class_implements( $className ) ) )
{
\IPS\Content\Search\Index::i()->massUpdate( $className, $this->_id, NULL, $permissions, NULL, $node->_id );
}
@@ -1644,23 +1699,12 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function disabledPermissions()
{
$disabled = array();
try
{
$guestGroup = \IPS\Member\Group::load( \IPS\Settings::i()->guest_group );
}
catch( \OutOfRangeException $e )
{
throw new \UnderflowException( 'invalid_guestgroup_admin', 199 );
}
if( $this->sub_can_post and !$this->can_view_others )
{
$disabled[ $guestGroup->g_id ][] = 'view';
return array( \IPS\Settings::i()->guest_group => array( 2, 3, 4, 5 ) );
}
return $disabled;
return array();
}
/**
@@ -1694,7 +1738,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
* This is used for search and the activity stream.
* We return a list of IDs and not node objects for memory efficiency.
*
* return null|array
* @return null|array
*/
public static function unsearchableNodeIds()
{
@@ -1705,7 +1749,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
/* For memory efficiency, we query the database directly rather than manage nodes */
$forums = iterator_to_array( \IPS\Db::i()->select( 'id', 'forums_forums', array( 'min_posts_view > ?', \IPS\Member::loggedIn()->member_posts ) )->setKeyField('id') );
static::$unsearchableNodeIds = count( $forums ) ? $forums : NULL;
static::$unsearchableNodeIds = \count( $forums ) ? $forums : NULL;
return static::$unsearchableNodeIds;
}
@@ -1714,19 +1758,38 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
* @return array
* @apiresponse int id ID number
* @apiresponse string name Forum name
* @apiresponse int topics Number of topics in forum
* @apiresponse string url URL
* @apiresponse int id ID number
* @apiresponse string name Forum name
* @apiresponse string path Forum name including parents (e.g. "Example Category > Example Forum")
* @apiresponse int topics Number of topics in forum
* @apiresponse string url URL
* @apiresponse int|null parentId Parent Node ID
* @clientapiresponse object|null permissions Node permissions
*/
public function apiOutput( \IPS\Member $authorizedMember = NULL )
{
return array(
$path = array();
foreach( $this->parents() AS $parent )
{
$path[] = $parent->_title;
}
$path[] = $this->_title;
$return = array(
'id' => $this->id,
'name' => $this->_title,
'path' => implode( ' > ', $path ),
'topics' => $this->topics,
'url' => (string) $this->url()
'url' => (string) $this->url(),
'parentId' => static::$databaseColumnParent ? $this->{static::$databaseColumnParent} : NULL
);
if( $authorizedMember === NULL )
{
$return['permissions'] = $this->permissions();
}
return $return;
}
/* !Clubs */
@@ -1742,6 +1805,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$itemClass = static::$contentItemClass;
$form->add( new \IPS\Helpers\Form\Text( 'club_node_name', $this->_id ? $this->_title : \IPS\Member::loggedIn()->language()->addToStack( $itemClass::$title . '_pl' ), TRUE, array( 'maxLength' => 255 ) ) );
$form->add( new \IPS\Helpers\Form\Editor( 'club_node_description', $this->_id ? \IPS\Member::loggedIn()->language()->get( static::$titleLangPrefix . $this->_id . '_desc' ) : NULL, FALSE, array( 'app' => 'forums', 'key' => 'Forums', 'autoSaveKey' => ( $this->id ? "forums-forum-{$this->id}" : "forums-new-forum" ), 'attachIds' => $this->id ? array( $this->id, NULL, 'description' ) : NULL, 'minimize' => 'forum_description_placeholder' ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_can_view_others_club', $this->id ? $this->can_view_others : TRUE ) );
}
/**
@@ -1758,6 +1822,19 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$this->name_seo = \IPS\Http\Url\Friendly::seoTitle( $values['club_node_name'] );
}
if( $this->can_view_others != $values['forum_can_view_others_club'] )
{
$this->can_view_others = $values['forum_can_view_others_club'];
if ( $this->_id )
{
$this->updateSearchIndexPermissions();
}
}
/* Use default priority for sitemaps */
$this->ipseo_priority = -1;
if ( !$this->_id )
{
$this->save();
@@ -1813,6 +1890,40 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return $simpleView;
}
/**
* Get the member's topic list view method
*
* @return string
* @throws \Exception
*/
public static function getMemberListView()
{
$method = ( isset( \IPS\Request::i()->cookie['forum_list_view'] ) ) ? \IPS\Request::i()->cookie['forum_list_view'] : NULL;
$chooseable = \IPS\Settings::i()->forums_view_list_choose;
if ( ! $chooseable )
{
return \IPS\Settings::i()->forums_view_list_method;
}
if ( ! $method )
{
try
{
$method = \IPS\Db::i()->select( 'method', 'forums_view_method', array( 'member_id=? and type=?', \IPS\Member::loggedIn()->member_id, 'list' ) )->first();
}
catch( \UnderFlowException $e )
{
$method = \IPS\Settings::i()->forums_view_list_method;
}
/* Attempt to set the cookie again */
\IPS\Request::i()->setCookie( 'forum_list_view', $method, ( new \IPS\DateTime )->add( new \DateInterval( 'P1Y' ) ) );
}
return $method;
}
/**
* Get the member's view method
@@ -1833,7 +1944,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
try
{
$method = \IPS\Db::i()->select( 'method', 'forums_view_method', array( 'member_id=?', \IPS\Member::loggedIn()->member_id ) )->first();
$method = \IPS\Db::i()->select( 'method', 'forums_view_method', array( 'member_id=? and type=?', \IPS\Member::loggedIn()->member_id, 'index' ) )->first();
}
catch( \UnderFlowException $e )
{
@@ -1844,7 +1955,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Request::i()->setCookie( 'forum_view', $method, ( new \IPS\DateTime )->add( new \DateInterval( 'P1Y' ) ) );
}
if ( ! $method or ( $chooseable != '*' AND ! in_array( $method, $chooseable ) ) )
if ( ! $method or ( $chooseable != '*' AND ! \in_array( $method, $chooseable ) ) )
{
$method = \IPS\Settings::i()->forums_default_view;
}
@@ -12,7 +12,7 @@
namespace IPS\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;
@@ -84,14 +84,9 @@ class _SavedAction extends \IPS\Node\Model
if ( $member->modPermission('can_use_saved_actions') and \IPS\forums\Topic::modPermission( 'use_saved_actions', $member, $forum ) )
{
if ( !isset( \IPS\Data\Store::i()->forumsSavedActions ) )
foreach ( static::getStore() as $action )
{
\IPS\Data\Store::i()->forumsSavedActions = iterator_to_array( \IPS\Db::i()->select( '*', 'forums_topic_mmod' ) );
}
foreach ( \IPS\Data\Store::i()->forumsSavedActions as $action )
{
if ( $action['mm_enabled'] and ( $action['mm_forums'] == '*' or in_array( $forum->id, array_filter( explode( ',', $action['mm_forums'] ) ) ) ) )
if ( $action['mm_enabled'] and ( $action['mm_forums'] == '*' or \in_array( $forum->id, array_filter( explode( ',', $action['mm_forums'] ) ) ) ) )
{
$return[ $action['mm_id'] ] = static::constructFromData( $action );
}
@@ -184,7 +179,7 @@ class _SavedAction extends \IPS\Node\Model
if( isset( $values['topic_move'] ) )
{
if ( !is_object( $values['topic_move'] ) and $values['topic_move'] == 0 )
if ( !\is_object( $values['topic_move'] ) and $values['topic_move'] == 0 )
{
$values['topic_move'] = -1;
}
@@ -211,7 +206,7 @@ class _SavedAction extends \IPS\Node\Model
}
/**
* Run
* Check Permissions and run the saved action
*
* @param \IPS\forums\Topic $topic The topic to run on
* @param \IPS\Member|NULL $member Member running (NULL for currently logged in member)
@@ -230,25 +225,38 @@ class _SavedAction extends \IPS\Node\Model
if ( $member->modPermission( 'forums' ) !== -1 AND $member->modPermission( 'forums' ) !== TRUE )
{
if ( !is_array( $member->modPermission( 'forums' ) ) or !in_array( $topic->container()->_id, $member->modPermission( 'forums' ) ) )
if ( !\is_array( $member->modPermission( 'forums' ) ) or !\in_array( $topic->container()->_id, $member->modPermission( 'forums' ) ) )
{
throw new \DomainException('NO_PERMISSION');
}
}
/* Check the action is enabled and allowed for the content item */
if ( !$this->mm_enabled )
{
throw new \DomainException('DISABLED');
}
if ( $this->mm_forums !== '*' and !in_array( $topic->container()->_id, explode( ',', $this->mm_forums ) ) )
{
throw new \DomainException('BAD_FORUM');
}
if ( $topic->isArchived() )
{
throw new \DomainException('TOPIC_ARCHIVED');
}
if ( $this->mm_forums !== '*' and !\in_array( $topic->container()->_id, explode( ',', $this->mm_forums ) ) )
{
throw new \DomainException('BAD_FORUM');
}
$this->_runOn( $topic, $member);
}
/**
* Run saved action
*
* @param \IPS\forums\Topic $topic The topic to run on
* @param \IPS\Member|NULL $member Member running (NULL for currently logged in member)
* @return void
*/
protected function _runOn( \IPS\forums\Topic $topic, \IPS\Member $member = NULL )
{
/* Archived Topics can't used saved actions */
if ( $topic->isArchived() )
{
throw new \DomainException('TOPIC_ARCHIVED');
}
/* Open/Close */
if ( $this->topic_state == 'open' )
{
@@ -258,7 +266,7 @@ class _SavedAction extends \IPS\Node\Model
{
$topic->state = 'closed';
}
/* Pin/Unpin */
if ( $this->topic_pin == 'pin' )
{
@@ -268,7 +276,7 @@ class _SavedAction extends \IPS\Node\Model
{
$topic->pinned = FALSE;
}
/* Title */
if ( $this->topic_title_st )
{
@@ -278,10 +286,10 @@ class _SavedAction extends \IPS\Node\Model
{
$topic->title .= $this->topic_title_end;
}
/* Save */
$topic->save();
/* Hide/Unhide */
if ( $this->topic_approve == 1 )
{
@@ -291,13 +299,13 @@ class _SavedAction extends \IPS\Node\Model
{
$topic->hide( $member );
}
/* Reply */
if ( $this->topic_reply )
{
$reply = \IPS\forums\Topic\Post::create( $topic, $this->topic_reply_content, FALSE, NULL, isset( $this->topic_reply_postcount ) AND $this->topic_reply_postcount ? TRUE : FALSE );
}
/* Move */
if ( $this->topic_move != -1 )
{
@@ -308,18 +316,7 @@ class _SavedAction extends \IPS\Node\Model
catch ( \Exception $e ) { }
}
}
/**
* Save Record
*
* @return void
*/
public function save()
{
parent::save();
unset( \IPS\Data\Store::i()->forumsSavedActions );
}
/**
* Delete Record
*
@@ -329,8 +326,26 @@ class _SavedAction extends \IPS\Node\Model
{
\IPS\File::unclaimAttachments( 'core_Admin', $this->mm_id, NULL, 'forumsSavedAction' );
parent::delete();
unset( \IPS\Data\Store::i()->forumsSavedActions );
}
/**
* @brief [ActiveRecord] Caches
* @note Defined cache keys will be cleared automatically as needed
*/
protected $caches = array( 'forumsSavedActions' );
/**
* Get data store
*
* @return array
*/
public static function getStore()
{
if ( !isset( \IPS\Data\Store::i()->forumsSavedActions ) )
{
\IPS\Data\Store::i()->forumsSavedActions = iterator_to_array( \IPS\Db::i()->select( '*', 'forums_topic_mmod' ) );
}
return \IPS\Data\Store::i()->forumsSavedActions;
}
}
@@ -12,7 +12,7 @@
namespace IPS\forums\Topic;
/* 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;
@@ -35,19 +35,28 @@ class _ArchivedPost extends Post
/**
* @brief [ActiveRecord] Database Connection
* @return \IPS\Db
*/
public static function db()
{
return ( !\IPS\Settings::i()->archive_remote_sql_host ) ? \IPS\Db::i() : \IPS\Db::i( 'archive', array(
'sql_host' => \IPS\Settings::i()->archive_remote_sql_host,
'sql_user' => \IPS\Settings::i()->archive_remote_sql_user,
'sql_pass' => \IPS\Settings::i()->archive_remote_sql_pass,
'sql_database' => \IPS\Settings::i()->archive_remote_sql_database,
'sql_port' => \IPS\Settings::i()->archive_sql_port,
'sql_socket' => \IPS\Settings::i()->archive_sql_socket,
'sql_tbl_prefix'=> \IPS\Settings::i()->archive_sql_tbl_prefix,
'sql_utf8mb4' => isset( \IPS\Settings::i()->sql_utf8mb4 ) ? \IPS\Settings::i()->sql_utf8mb4 : FALSE
) );
/* Connect to the remote DB if needed */
if ( \IPS\CIC2 )
{
return \IPS\Cicloud\getForumArchiveDb();
}
else
{
return ( !\IPS\Settings::i()->archive_remote_sql_host ) ? \IPS\Db::i() : \IPS\Db::i( 'archive', array(
'sql_host' => \IPS\Settings::i()->archive_remote_sql_host,
'sql_user' => \IPS\Settings::i()->archive_remote_sql_user,
'sql_pass' => \IPS\Settings::i()->archive_remote_sql_pass,
'sql_database' => \IPS\Settings::i()->archive_remote_sql_database,
'sql_port' => \IPS\Settings::i()->archive_sql_port,
'sql_socket' => \IPS\Settings::i()->archive_sql_socket,
'sql_tbl_prefix'=> \IPS\Settings::i()->archive_sql_tbl_prefix,
'sql_utf8mb4' => isset( \IPS\Settings::i()->sql_utf8mb4 ) ? \IPS\Settings::i()->sql_utf8mb4 : FALSE
) );
}
}
/**
@@ -107,7 +116,7 @@ class _ArchivedPost extends Post
array( \IPS\Db::i()->in( 'archive_forum_id', iterator_to_array( \IPS\Db::i()->select( 'id', 'forums_forums', 'inc_postcount=0' ) ), TRUE ) )
) )->first();
}
/**
* Joins (when loading comments)
*
+69 -52
View File
@@ -12,7 +12,7 @@
namespace IPS\forums\Topic;
/* 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;
@@ -125,7 +125,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
*/
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
{
$comment = call_user_func_array( 'parent::create', func_get_args() );
$comment = parent::create( $item, $comment, $first, $guestName, $incrementPostCount, $member, $time, $ipAddress, $hiddenStatus );
if ( !$comment->hidden() )
{
@@ -165,7 +165,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
*
* @param \IPS\Node\Model|NULL $container Container
* @return void
* @see \IPS\forums\Topic\Post::incrementPostCount()
* @see incrementPostCount
*/
public static function incrementPostCount( \IPS\Node\Model $container = NULL )
{
@@ -186,6 +186,18 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
\IPS\Db::i()->select( 'id', 'forums_forums', 'inc_postcount=1' )
) )->join( 'forums_topics', 'tid=topic_id' )->first() ;
}
/**
* Post count for member
*
* @param \IPS\Member $member The memner
* @return int
*/
public static function rawMemberPostCount( \IPS\Member $member )
{
/* Explicitly calling to the parent so we don't filter by forum ID */
return parent::memberPostCount( $member );
}
/**
* Get items with permission check
@@ -200,7 +212,9 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
* @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data)
* @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data)
* @return \IPS\Patterns\ActiveRecordIterator|int
* @param bool $countOnly If true will return the count
* @param array|null $joins Additional arbitrary joins for the query
* @return array|NULL|\IPS\Content\Comment If $limit is 1, will return \IPS\Content\Comment or NULL for no results. For any other number, will return an array.
*/
public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenItems=\IPS\Content\Hideable::FILTER_AUTOMATIC, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL )
{
@@ -246,7 +260,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
}
/* Minimum posts */
elseif ( $containerData['min_posts_view'] and $containerData['min_posts_view'] >= \IPS\Member::loggedIn()->member_posts )
elseif ( $containerData['min_posts_view'] and $containerData['min_posts_view'] > \IPS\Member::loggedIn()->member_posts )
{
return \IPS\Theme::i()->getTemplate( 'global', 'forums' )->searchNoPermission( \IPS\Member::loggedIn()->language()->addToStack( 'no_perm_post_min_posts', FALSE, array( 'pluralize' => array( $containerData['min_posts_view'] ) ) ) );
}
@@ -282,7 +296,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
public function canVote( $rating=0, $member=NULL )
{
/* Is $rating valid */
if ( !in_array( $rating, array( -1, 0, 1 ) ) )
if ( !\in_array( $rating, array( -1, 0, 1 ) ) )
{
throw new \InvalidArgumentException;
}
@@ -311,17 +325,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
{
return FALSE;
}
/* Have we already voted? */
if ( $rating !== 0 or !\IPS\Settings::i()->forums_answers_downvote )
{
$ratings = $this->item()->answerVotes( $member );
if ( isset( $ratings[ $this->pid ] ) and $ratings[ $this->pid ] === $rating )
{
return FALSE;
}
}
return TRUE;
}
@@ -353,13 +357,6 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
$item = NULL;
}
/* Reset best answer if relevant */
if ( $item !== NULL AND $item->topic_answered_pid == $this->pid )
{
$item->topic_answered_pid = FALSE;
$item->save();
}
parent::delete();
/* Deleting a post may make the item no longer popular */
@@ -369,10 +366,23 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
}
}
/**
* Get the snippet (stripped of HTML)
*
* @param int $length The length of the snippet to return
* @return string
*/
public function snippet( $length=300 )
{
$contentField = static::$databaseColumnMap['content'];
$content = trim( str_replace( \chr(0xC2) . \chr(0xA0), ' ', strip_tags( preg_replace( "/(<br(?: \/)?>|<\/p>)/i", ' ', preg_replace( "#<blockquote(?:[^>]+?)>.+?(?<!<blockquote)</blockquote>#s", " ", preg_replace( "#<script(.*?)>(.*)</script>#uis", "", ' ' . $this->$contentField . ' ' ) ) ) ) ) );
return mb_substr( $content, 0, $length ) . ( mb_strlen( $content ) > $length ? '&hellip;' : '' );
}
/**
* Indefinite Article
*
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @param \IPS\Lang|NULL $lang The language to use, or NULL for the language of the currently logged in member
* @return string
*/
public function indefiniteArticle( \IPS\Lang $lang = NULL )
@@ -411,8 +421,8 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
/**
* Definite Article
*
* @param array $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang|NULL $lang The language to use, or NULL for the language of the currently logged in member
* @param array $options Options to pass to \IPS\Lang::addToStack
* @return string
*/
@@ -468,6 +478,17 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
return 'pid';
}
/**
* Set this comment as solved
*
* @param boolean $value TRUE/FALSE value
* @return void
*/
public function setSolved( $value )
{
$this->post_bwoptions['best_answer'] = (bool) $value;
}
/**
* Get content for embed
*
@@ -481,34 +502,30 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
}
/**
* Check Moderator Permission
* Create a query to fetch the "top members"
*
* @param string $type 'edit', 'hide', 'unhide', 'delete', etc.
* @param \IPS\Member|NULL $member The member to check for or NULL for the currently logged in member
* @param \IPS\Node\Model|NULL $container The container
* @return bool
* @note The intention is to formulate a query that will fetch the members with the most contributions
* @note The group by confuses MySQL and it ends up using the 'queued' index, when the 'author_id' index is far better
* @param int $limit The number of members to return
* @return \IPS\Db\Select
*/
public static function modPermission( $type, \IPS\Member $member = NULL, \IPS\Node\Model $container = NULL )
public static function topMembersQuery( $limit )
{
/* If this is called from a gateway script, i.e. email piping, just return false as we are a "guest" */
if( $member === NULL AND !\IPS\Dispatcher::hasInstance() )
{
return FALSE;
}
if ( \in_array( $type, array( 'edit', 'hide', 'unhide', 'delete' ) ) )
{
/* Load Member */
$member = $member ?: \IPS\Member::loggedIn();
/* Post related permission */
if ( $member->modPermission( "can_{$type}_post" ) )
{
return TRUE;
}
}
return parent::modPermission($type, $member, $container );
$query = parent::topMembersQuery( $limit );
return $query->forceIndex( 'author_id' );
}
/**
* Webhook filters
*
* @return array
*/
public function webhookFilters()
{
$return = parent::webhookFilters();
$return['forums'] = $this->item()->container()->_id;
$return['hasBestAnswer'] = (bool) $this->item()->topic_answered_pid;
return $return;
}
}
+405 -146
View File
@@ -12,7 +12,7 @@
namespace IPS\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;
@@ -35,12 +35,31 @@ class _Topic extends \IPS\Content\Item implements
\IPS\Content\Embeddable,
\IPS\Content\MetaData
{
use \IPS\Content\Reportable;
use \IPS\Content\Reactable, \IPS\Content\Reportable, \IPS\Content\Statistics, \IPS\Content\Solvable;
/**
* @brief Not archived
*/
const ARCHIVE_NOT = 0;
/**
* @brief Archiving completed
*/
const ARCHIVE_DONE = 1;
/**
* @brief In the process of being archived
*/
const ARCHIVE_WORKING = 2;
/**
* @brief Excluded from archiving
*/
const ARCHIVE_EXCLUDE = 3;
/**
* @brief Flagged to restore from archive
*/
const ARCHIVE_RESTORE = 4;
/**
@@ -86,7 +105,7 @@ class _Topic extends \IPS\Content\Item implements
'unapproved_comments' => 'topic_queuedposts',
'hidden_comments' => 'topic_hiddenposts',
'first_comment_id' => 'topic_firstpost',
'last_comment' => array( 'last_real_post', 'last_post' ),
'last_comment' => array( 'last_post', 'last_real_post' ),
'last_comment_by' => 'last_poster_id',
'last_comment_name' => 'last_poster_name',
'views' => 'views',
@@ -99,7 +118,8 @@ class _Topic extends \IPS\Content\Item implements
'featured' => 'featured',
'state' => 'state',
'updated' => 'last_post',
'meta_data' => 'topic_meta_data'
'meta_data' => 'topic_meta_data',
'solved_comment_id' => 'topic_answered_pid'
);
/**
@@ -146,6 +166,11 @@ class _Topic extends \IPS\Content\Item implements
* @brief Hover preview
*/
public $tableHoverUrl = TRUE;
/**
* @brief Setting name for show_meta
*/
public static $showMetaSettingKey = 'forums_topics_show_meta';
/**
* Callback from \IPS\Http\Url\Inernal::correctUrlFromVerifyClass()
@@ -190,14 +215,14 @@ class _Topic extends \IPS\Content\Item implements
}
/* Did we have any? */
if( count( $paramsToSet ) )
if( \count( $paramsToSet ) )
{
if( $url === NULL )
{
$url = $this->url();
}
if( count( $paramsToUnset ) )
if( \count( $paramsToUnset ) )
{
$url = $url->stripQueryString( $paramsToUnset );
}
@@ -303,14 +328,14 @@ class _Topic extends \IPS\Content\Item implements
public static function getItemsWithPermissionWhere( $where, $permissionKey, $member, &$joinContainer, $skipPermission=FALSE )
{
/* Don't show topics in password protected forums */
if ( !$skipPermission and in_array( $permissionKey, array( 'view', 'read' ) ) )
if ( !$skipPermission and \in_array( $permissionKey, array( 'view', 'read' ) ) )
{
$joinContainer = TRUE;
$member = $member ?: \IPS\Member::loggedIn();
$whereString = 'forums_forums.password=? OR ' . \IPS\Db::i()->findInSet( 'forums_forums.password_override', $member->groups );
$whereParams = array( NULL );
if ( \IPS\Dispatcher::hasInstance() AND $member === \IPS\Member::loggedIn() )
if ( \IPS\Dispatcher::hasInstance() AND $member->member_id === \IPS\Member::loggedIn()->member_id )
{
foreach ( \IPS\Request::i()->cookie as $k => $v )
{
@@ -325,43 +350,82 @@ class _Topic extends \IPS\Content\Item implements
$where['container'][] = array_merge( array( '( ' . $whereString . ' )' ), $whereParams );
}
/* Don't show topics from forums in which topics only show to the poster */
if ( $skipPermission !== TRUE and in_array( $permissionKey, array( 'view', 'read' ) ) )
if ( $skipPermission !== TRUE and \in_array( $permissionKey, array( 'view', 'read' ) ) )
{
$member = $member ?: \IPS\Member::loggedIn();
if ( !$member->modPermission( 'can_read_all_topics' ) or ( $member->modPermission( 'forums' ) !== TRUE and $member->modPermission( 'forums' ) !== -1 ) )
if ( $skipPermission instanceof \IPS\forums\Forum )
{
if ( $skipPermission instanceof \IPS\forums\Forum )
if ( !$skipPermission->can_view_others )
{
if ( !$skipPermission->can_view_others )
if ( !$member->member_id )
{
return array( '1=0' );
}
if ( $club = $skipPermission->club() )
{
if ( !$club->isModerator( $member ) )
{
$where['item'][] = array( 'forums_topics.starter_id=?', $member->member_id );
}
}
elseif ( !$member->modPermission('can_read_all_topics') or ( $member->modPermission('forums') !== TRUE and !\in_array( $skipPermission->_id, $member->modPermission('forums') ) ) )
{
$where['item'][] = array( 'forums_topics.starter_id=?', $member->member_id );
}
}
}
elseif ( !$member->modPermission('can_read_all_topics') or \is_array( $member->modPermission('forums') ) or !$member->modPermission('can_access_all_clubs') )
{
$joinContainer = TRUE;
if ( !$member->member_id )
{
$where[] = array( 'forums_forums.can_view_others=1' );
}
else
{
$joinContainer = TRUE;
$whereClause = array( 'forums_forums.can_view_others=1 OR forums_topics.starter_id=?', (int) $member->member_id );
$ors = array();
$whereClause = array( '( forums_forums.can_view_others=1 OR forums_topics.starter_id=? )', (int) $member->member_id );
if ( $member->modPermission( 'can_read_all_topics' ) )
if ( $member->modPermission('can_read_all_topics') )
{
$forums = $member->modPermission( 'forums' );
if ( isset( $forums ) and is_array ( $forums ) )
$forums = $member->modPermission('forums');
if ( isset( $forums ) and \is_array( $forums ) )
{
$whereClause[0] = "( {$whereClause[0]} OR " . \IPS\Db::i()->in( 'forums_topics.forum_id', $forums ) . ' )';
$ors[] = '( forums_forums.club_id IS NULL AND ' . \IPS\Db::i()->in( 'forums_topics.forum_id', $forums ) . ')';
}
else
{
$ors[] = 'forums_forums.club_id IS NULL';
}
}
if ( $member->modPermission('can_access_all_clubs') )
{
$ors[] = 'forums_forums.club_id IS NOT NULL';
}
elseif ( $moderatorClubIds = $member->clubs( FALSE, TRUE ) )
{
$ors[] = \IPS\Db::i()->in( 'forums_forums.club_id', $moderatorClubIds );
}
if ( $ors )
{
$whereClause[0] = "( {$whereClause[0]} OR " . implode( ' OR ', $ors ) . ' )';
}
else
{
$whereClause[0] = "( {$whereClause[0]} )";
}
$where[] = $whereClause;
}
}
}
}
/* Don't show topics in forums we can't view because our post count is too low */
if ( !$skipPermission and in_array( $permissionKey, array( 'view', 'read' ) ) )
if ( !$skipPermission and \in_array( $permissionKey, array( 'view', 'read' ) ) )
{
$member = $member ?: \IPS\Member::loggedIn();
$joinContainer = TRUE;
@@ -373,7 +437,7 @@ class _Topic extends \IPS\Content\Item implements
}
/**
* Total item count (including children)
* Total item \count(including children)
*
* @param \IPS\Node\Model $container The container
* @param bool $includeItems If TRUE, items will be included (this should usually be true)
@@ -436,8 +500,8 @@ class _Topic extends \IPS\Content\Item implements
$current[] = 'pin';
}
}
if ( static::modPermission( 'hide', NULL, $container ) )
$canHide = ( $item ) ? $item->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 ( static::modPermission( 'hide', NULL, $container ) or $canHide )
{
$options['hide'] = 'create_topic_hidden';
$toggles['hide'] = array( 'create_topic_hidden' );
@@ -457,7 +521,7 @@ class _Topic extends \IPS\Content\Item implements
}
}
if ( count( $options ) or count ( $toggles ) )
if ( \count( $options ) or \count( $toggles ) )
{
$formElements['topic_state'] = new \IPS\Helpers\Form\CheckboxSet( 'topic_create_state', $current, FALSE, array(
'options' => $options,
@@ -514,17 +578,17 @@ class _Topic extends \IPS\Content\Item implements
{
if ( static::modPermission( 'lock', NULL, $this->container() ) )
{
$this->state = ( in_array( 'lock', $values['topic_create_state'] ) ) ? 'closed' : 'open';
$this->state = ( \in_array( 'lock', $values['topic_create_state'] ) ) ? 'closed' : 'open';
}
if ( static::modPermission( 'pin', NULL, $this->container() ) )
{
$this->pinned = ( in_array( 'pin', $values['topic_create_state'] ) ) ? 1 : 0;
$this->pinned = ( \in_array( 'pin', $values['topic_create_state'] ) ) ? 1 : 0;
}
if ( static::modPermission( 'feature', NULL, $this->container() ) )
{
$this->featured = ( in_array( 'feature', $values['topic_create_state'] ) ) ? 1 : 0;
$this->featured = ( \in_array( 'feature', $values['topic_create_state'] ) ) ? 1 : 0;
}
}
@@ -534,7 +598,7 @@ class _Topic extends \IPS\Content\Item implements
$this->topic_open_time = !empty( $values['topic_open_time'] ) ? $values['topic_open_time']->getTimestamp() : 0;
$this->topic_close_time = !empty( $values['topic_close_time'] ) ? $values['topic_close_time']->getTimestamp() : 0;
if( isset( $values['topic_create_state'] ) and !in_array( 'lock', $values['topic_create_state'] ) )
if( isset( $values['topic_create_state'] ) and !\in_array( 'lock', $values['topic_create_state'] ) )
{
$this->state = 'open';
}
@@ -544,6 +608,12 @@ class _Topic extends \IPS\Content\Item implements
{
$this->state = 'closed';
}
/* If we specified an unlock time, but no lock time, make sure the topic is locked */
if ( $this->topic_open_time and !$this->topic_close_time )
{
$this->state = 'closed';
}
}
}
@@ -604,9 +674,9 @@ class _Topic extends \IPS\Content\Item implements
/* Moderator actions */
if ( isset( $values['topic_create_state'] ) )
{
if ( static::modPermission( 'hide', NULL, $this->container() ) )
if ( $this->canHide() )
{
if( in_array( 'hide', $values['topic_create_state'] ) )
if( \in_array( 'hide', $values['topic_create_state'] ) )
{
$this->hide( NULL );
}
@@ -630,7 +700,7 @@ class _Topic extends \IPS\Content\Item implements
public static $urlBase = 'app=forums&module=forums&controller=topic&id=';
/**
* @brief URL Base
* @brief URL Template
*/
public static $urlTemplate = 'forums_topic';
@@ -642,7 +712,7 @@ class _Topic extends \IPS\Content\Item implements
/**
* Stats for table view
*
* @param bool $includeFirstCommentInCommentCount Determines whether the first comment should be inlcluded in the comment count (e.g. For "posts", use TRUE. For "replies", use FALSE)
* @param bool $includeFirstCommentInCommentCount Determines whether the first comment should be inlcluded in the comment \count(e.g. For "posts", use TRUE. For "replies", use FALSE)
* @return array
*/
public function stats( $includeFirstCommentInCommentCount=TRUE )
@@ -682,7 +752,7 @@ class _Topic extends \IPS\Content\Item implements
* Set the new popular time if needed
*
*/
public function rebuildPopularTime( )
public function rebuildPopularTime()
{
$popularNowSettings = json_decode( \IPS\Settings::i()->forums_popular_now, TRUE );
@@ -694,9 +764,9 @@ class _Topic extends \IPS\Content\Item implements
\IPS\Db::i()->select( '*', 'forums_posts', array( 'queued IN(0,2) AND post_date >? AND topic_id=?', \IPS\DateTime::create()->sub( $popularNowInterval )->getTimestamp(), $this->tid ), 'post_date DESC', NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER ),
'IPS\\forums\\Topic\\Post'
) );
if ( count( $comments ) >= $popularNowSettings['posts'] )
if ( \count( $comments ) >= $popularNowSettings['posts'] )
{
$commentToBasePopularNowTimeOff = array_slice( $comments, ( $popularNowSettings['posts'] - 1 ), 1 );
$commentToBasePopularNowTimeOff = \array_slice( $comments, ( $popularNowSettings['posts'] - 1 ), 1 );
$commentToBasePopularNowTimeOff = array_pop( $commentToBasePopularNowTimeOff );
$this->popular_time = \IPS\DateTime::ts( $commentToBasePopularNowTimeOff->post_date )->add( $popularNowInterval )->getTimestamp();
@@ -760,29 +830,10 @@ class _Topic extends \IPS\Content\Item implements
}
}
if ( !$this->container()->can_view_others )
$member = $member ?: \IPS\Member::loggedIn();
if ( $member != $this->author() and !$this->container()->memberCanAccessOthersTopics( $member ) )
{
$member = $member ?: \IPS\Member::loggedIn();
if ( $member != $this->author() )
{
if
(
(
$member->modPermission( 'forums' ) !== -1
and
(
is_array( $member->modPermission( 'forums' ) )
and
!in_array( $this->container()->_id, $member->modPermission( 'forums' ) )
)
)
or
!$member->modPermission( 'can_read_all_topics' )
)
{
return FALSE;
}
}
return FALSE;
}
return TRUE;
@@ -800,7 +851,7 @@ class _Topic extends \IPS\Content\Item implements
{
$return = $this->container()->searchIndexPermissions();
if ( $this->starter_id and !$this->container()->can_view_others )
if ( !$this->container()->can_view_others )
{
/* If the search index permissions are empty, just return now because no one can see content in this forum */
if( !$return )
@@ -918,34 +969,36 @@ class _Topic extends \IPS\Content\Item implements
/**
* Should new items be moderated?
*
* @param \IPS\Member $member The member posting
* @param \IPS\Node\Model $container The container
* @param \IPS\Member $member The member posting
* @param \IPS\Node\Model $container The container
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will check if a newly registered member would be moderated
* @return bool
*/
public static function moderateNewItems( \IPS\Member $member, \IPS\Node\Model $container = NULL )
public static function moderateNewItems( \IPS\Member $member, \IPS\Node\Model $container = NULL, $considerPostBeforeRegistering = FALSE )
{
if ( $container and ( $container->preview_posts == 1 or $container->preview_posts == 2 ) and !$member->group['g_avoid_q'] )
{
return !static::modPermission( 'approve', $member, $container );
}
return parent::moderateNewItems( $member, $container );
return parent::moderateNewItems( $member, $container, $considerPostBeforeRegistering );
}
/**
* Should new comments be moderated?
*
* @param \IPS\Member $member The member posting
* @param \IPS\Member $member The member posting
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will check if a newly registered member would be moderated
* @return bool
*/
public function moderateNewComments( \IPS\Member $member )
public function moderateNewComments( \IPS\Member $member, $considerPostBeforeRegistering = FALSE )
{
if ( ( $this->container()->preview_posts == 1 or $this->container()->preview_posts == 3 ) and !$member->group['g_avoid_q'] )
{
return TRUE;
}
return parent::moderateNewComments( $member );
return parent::moderateNewComments( $member, $considerPostBeforeRegistering );
}
/**
@@ -1013,6 +1066,27 @@ class _Topic extends \IPS\Content\Item implements
/* If this is a QA forum we need to make sure we only have one best answer (at most) post-merge */
if( $this->isQuestion() )
{
/* Combine question ratings */
$itemTids = array();
foreach( $items as $item )
{
/* We will use UPDATE IGNORE for the update queries, as duplicate key errors could result if we upvoted both topics */
\IPS\Db::i()->update( 'forums_question_ratings', array( 'forum' => $this->forum_id, 'topic' => $this->tid ), array( 'topic=?', $item->tid ), array(), NULL, \IPS\Db::IGNORE );
\IPS\Db::i()->update( 'forums_answer_ratings', array( 'topic' => $this->tid ), array( 'topic=?', $item->tid ), array(), NULL, \IPS\Db::IGNORE );
$itemTids[] = $item->tid;
}
/* If any rows still exist for the old topic, that means it was a duplicate and we can remove */
\IPS\Db::i()->delete( 'forums_question_ratings', "topic IN(" . implode( ',', $itemTids ) . ")" );
\IPS\Db::i()->delete( 'forums_answer_ratings', "topic IN(" . implode( ',', $itemTids ) . ")" );
/* And then update the topic with the new question rating count */
\IPS\Db::i()->update( 'forums_topics', array( 'question_rating' =>
\IPS\Db::i()->select( 'SUM(rating)', 'forums_question_ratings', array( 'topic=?', $this->tid ) )
), array( 'tid=?', $this->tid ) );
/* Does this topic already have a best answer? */
if( $this->topic_answered_pid )
{
@@ -1065,6 +1139,9 @@ class _Topic extends \IPS\Content\Item implements
}
}
/* Update popular time */
$this->rebuildPopularTime();
return parent::mergeIn( $items, $keepLinks );
}
@@ -1133,7 +1210,7 @@ class _Topic extends \IPS\Content\Item implements
* @param string $action The action
* @param \IPS\Member|NULL $member The member doing the action (NULL for currently logged in member)
* @param string|NULL $reason Reason (for hides)
* @param bool $immedaitely Delete Immediately
* @param bool $immediately Delete Immediately
* @return void
* @throws \OutOfRangeException|\InvalidArgumentException|\RuntimeException
*/
@@ -1141,14 +1218,14 @@ class _Topic extends \IPS\Content\Item implements
{
if ( mb_substr( $action, 0, 12 ) === 'savedAction-' )
{
$action = \IPS\forums\SavedAction::load( intval( mb_substr( $action, 12 ) ) );
$action = \IPS\forums\SavedAction::load( \intval( mb_substr( $action, 12 ) ) );
$action->runOn( $this );
/* Log */
\IPS\Session::i()->modLog( 'modlog__saved_action', array( 'forums_mmod_' . $action->mm_id => TRUE, $this->url()->__toString() => FALSE, $this->mapped( 'title' ) => FALSE ), $this );
}
call_user_func_array( 'parent::modAction', func_get_args() );
parent::modAction( $action, $member, $reason, $immediately );
/* Prevent topics with an open time re-opening again after being locked */
if ( $action == 'lock' )
@@ -1208,9 +1285,40 @@ class _Topic extends \IPS\Content\Item implements
}
/* !Questions & Answers */
/**
* Any container has solvable enabled?
*
* @return boolean
*/
public static function anyContainerAllowsSolvable()
{
return (bool) \IPS\Db::i()->select( 'COUNT(*)', 'forums_forums', '(' . \IPS\Db::i()->bitwiseWhere( \IPS\forums\Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers' ) . ') OR ( ' . \IPS\Db::i()->bitwiseWhere( \IPS\forums\Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers_moderator' ) . ' )' )->first();
}
/**
* Is this topic a question?
* Container has solvable enabled
*
* @return string
*/
public function containerAllowsSolvable()
{
return $this->container()->forums_bitoptions['bw_enable_answers_moderator'];
}
/**
* Container has solvable enabled
*
* @return string
*/
public function containerAllowsMemberSolvable()
{
return $this->container()->forums_bitoptions['bw_enable_answers_member'];
}
/**
* Is this topic a question? \IPS\forums\Forum::$modPerm
*
* @return bool
*/
@@ -1232,9 +1340,10 @@ class _Topic extends \IPS\Content\Item implements
{
return FALSE;
}
/* If we asked this question, we can set the best answer */
$member = $member ?: \IPS\Member::loggedIn();
/* If we asked this question, we can set the best answer */
if ( $member == $this->author() and $this->container()->forums_bitoptions['bw_enable_answers_member'] )
{
return TRUE;
@@ -1249,9 +1358,9 @@ class _Topic extends \IPS\Content\Item implements
( $member->modPermission( \IPS\forums\Forum::$modPerm ) === TRUE or $member->modPermission( \IPS\forums\Forum::$modPerm ) === -1 )
or
(
is_array( $member->modPermission( \IPS\forums\Forum::$modPerm ) )
\is_array( $member->modPermission( \IPS\forums\Forum::$modPerm ) )
and
in_array( $this->container()->_id, $member->modPermission( \IPS\forums\Forum::$modPerm ) )
\in_array( $this->container()->_id, $member->modPermission( \IPS\forums\Forum::$modPerm ) )
)
)
) {
@@ -1278,7 +1387,7 @@ class _Topic extends \IPS\Content\Item implements
if ( !isset( $this->answerVotes[ $member->member_id ] ) )
{
$this->answerVotes[ $member->member_id ] = iterator_to_array(
\IPS\Db::i()->select( 'post,rating', 'forums_answer_ratings', array( 'topic=? AND member=?', $this->tid, $member->member_id ) )
\IPS\Db::i()->select( 'post,rating', 'forums_answer_ratings', array( 'topic=? AND `member`=?', $this->tid, $member->member_id ) )
->setKeyField( 'post' )
->setValueField( 'rating' )
);
@@ -1316,7 +1425,7 @@ class _Topic extends \IPS\Content\Item implements
public function canVote( $rating=0, $member=NULL )
{
/* Is $rating valid */
if ( !in_array( $rating, array( -1, 0, 1 ) ) )
if ( !\in_array( $rating, array( -1, 0, 1 ) ) )
{
throw new \InvalidArgumentException;
}
@@ -1339,17 +1448,7 @@ class _Topic extends \IPS\Content\Item implements
{
return FALSE;
}
/* Have we already voted? */
if ( $rating !== 0 or !\IPS\Settings::i()->forums_questions_downvote )
{
$ratings = $this->votes();
if ( isset( $ratings[ $member->member_id ] ) and $ratings[ $member->member_id ] === $rating )
{
return FALSE;
}
}
/* Downvoting disabled? */
if ( $rating === -1 and !\IPS\Settings::i()->forums_questions_downvote and ( !isset( $ratings[ $member->member_id ] ) or $ratings[ $member->member_id ] != 1 ) )
{
@@ -1374,7 +1473,7 @@ class _Topic extends \IPS\Content\Item implements
if ( $this->votes === NULL )
{
$this->votes = iterator_to_array(
\IPS\Db::i()->select( 'member,rating', 'forums_question_ratings', array( 'topic=?', $this->tid ) )
\IPS\Db::i()->select( '`member`,rating', 'forums_question_ratings', array( 'topic=?', $this->tid ) )
->setKeyField( 'member' )
->setValueField( 'rating' )
);
@@ -1409,7 +1508,8 @@ class _Topic extends \IPS\Content\Item implements
/**
* Indefinite Article
*
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @param array $containerData Data about the container
* @param \IPS\Lang|NULL $lang The language to use, or NULL for the language of the currently logged in member
* @return string
*/
public static function _indefiniteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL )
@@ -1431,7 +1531,7 @@ class _Topic extends \IPS\Content\Item implements
* Definite Article
*
* @param array $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @param \IPS\Lang|NULL $lang The language to use, or NULL for the language of the currently logged in member
* @param array $options Options to pass to \IPS\Lang::addToStack
* @return string
*/
@@ -1486,7 +1586,7 @@ class _Topic extends \IPS\Content\Item implements
*/
public function isArchived()
{
return in_array( $this->topic_archive_status, array( static::ARCHIVE_DONE, static::ARCHIVE_WORKING, static::ARCHIVE_RESTORE ) );
return \in_array( $this->topic_archive_status, array( static::ARCHIVE_DONE, static::ARCHIVE_WORKING, static::ARCHIVE_RESTORE ) );
}
/**
@@ -1532,7 +1632,7 @@ class _Topic extends \IPS\Content\Item implements
$taskData = \IPS\Db::i()->select( '*', 'core_tasks', array( '`key`=? AND app=?', 'archive', 'forums' ) )->first();
$time = \IPS\DateTime::ts( $taskData['next_run'] );
$postsToBeUnarchived = \IPS\Db::i()->select( 'SUM(posts) + COUNT(*)', 'forums_topics', array( 'topic_archive_status=?', static::ARCHIVE_RESTORE ) )->first();
$postsToBeUnarchived = \IPS\Db::i()->select( 'SUM(posts) + count(*)', 'forums_topics', array( 'topic_archive_status=?', static::ARCHIVE_RESTORE ) )->first();
if ( $postsToBeUnarchived AND $postsToBeUnarchived > \IPS\forums\tasks\unarchive::PROCESS_PER_BATCH )
{
@@ -1550,17 +1650,18 @@ class _Topic extends \IPS\Content\Item implements
/**
* Can comment?
*
* @param \IPS\Member\NULL $member The member (NULL for currently logged in member)
* @param \IPS\Member\NULL $member The member (NULL for currently logged in member)
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will return TRUE if "Post Before Registering" feature is enabled
* @return bool
*/
public function canComment( $member=NULL )
public function canComment( $member=NULL, $considerPostBeforeRegistering = TRUE )
{
if ( $this->isArchived() )
{
return FALSE;
}
return parent::canComment( $member );
return parent::canComment( $member, $considerPostBeforeRegistering );
}
/**
@@ -1704,6 +1805,11 @@ class _Topic extends \IPS\Content\Item implements
return FALSE;
}
if ( $this->moved_to )
{
return FALSE;
}
return parent::canMerge( $member );
}
@@ -1726,7 +1832,7 @@ class _Topic extends \IPS\Content\Item implements
/**
* Can Feature a Comment
*
* @param \IPS\Member|NULL The member, or NULL for currently logged in
* @param \IPS\Member|NULL $member The member, or NULL for currently logged in
* @return bool
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
@@ -1743,8 +1849,8 @@ class _Topic extends \IPS\Content\Item implements
/**
* Can perform an action on a message
*
* @param string The action
* @param \IPS\Member|NULL The member, or NULL for currently logged in
* @param string $action The action
* @param \IPS\Member|NULL $member The member, or NULL for currently logged in
* @return bool
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
@@ -1758,6 +1864,22 @@ class _Topic extends \IPS\Content\Item implements
return parent::canOnMessage( $action, $member );
}
/**
* Can toggle item-level moderation?
*
* @param \IPS\Member|NULL $member
* @return bool
*/
public function canToggleItemModeration( ?\IPS\Member $member = NULL ): bool
{
if ( $this->isArchived() )
{
return FALSE;
}
return parent::canToggleItemModeration( $member );
}
/**
* Get comments
*
@@ -1778,7 +1900,7 @@ class _Topic extends \IPS\Content\Item implements
{
static $comments = array();
$idField = static::$databaseColumnId;
$_hash = md5( $this->$idField . json_encode( func_get_args() ) );
$_hash = md5( $this->$idField . json_encode( \func_get_args() ) );
if( !$bypassCache and isset( $comments[ $_hash ] ) )
{
@@ -1800,7 +1922,7 @@ class _Topic extends \IPS\Content\Item implements
if( $extraWhereClause !== NULL )
{
if( is_array( $extraWhereClause ) )
if( \is_array( $extraWhereClause ) )
{
foreach( $extraWhereClause as $k => $v )
{
@@ -1817,9 +1939,27 @@ class _Topic extends \IPS\Content\Item implements
{
$class = static::$commentClass;
}
$comments[ $_hash ] = $this->_comments( $class, $limit ?: static::getCommentsPerPage(), $offset, ( isset( $class::$databaseColumnMap[ $order ] ) ? ( $class::$databasePrefix . $class::$databaseColumnMap[ $order ] ) : $order ) . ' ' . $orderDirection, $member, $includeHiddenComments, $cutoff, $includeWarnings, $extraWhereClause, $includeDeleted );
try
{
$comments[ $_hash ] = $this->_comments( $class, $limit ?: static::getCommentsPerPage(), $offset, ( isset( $class::$databaseColumnMap[ $order ] ) ? ( $class::$databasePrefix . $class::$databaseColumnMap[ $order ] ) : $order ) . ' ' . $orderDirection, $member, $includeHiddenComments, $cutoff, $includeWarnings, $extraWhereClause, $includeDeleted );
}
catch( \IPS\Db\Exception $e )
{
$post = new \IPS\forums\Topic\Post;
$post->topic_id = $this->tid;
$post->post = '<p><em>' . \IPS\Member::loggedIn()->language()->addToStack('archived_topic_missing_posts') . '</em></p>';
$post->post_date = $this->start_date;
$post->author_id = $this->starter_id;
if ( \IPS\Member::loggedIn()->isAdmin() )
{
$post->post .= "<p>" . \IPS\Member::loggedIn()->language()->addToStack('archived_topic_missing_posts_admin') . "</p><p><strong>{$e->getMessage()}<br><textarea>" . var_export( $e, TRUE ) . '</textarea></p>';
}
$comments[ $_hash ] = array( $post );
}
/* Restore comment class now */
if( $commentClass )
{
@@ -1839,6 +1979,30 @@ class _Topic extends \IPS\Content\Item implements
return parent::resyncCommentCounts( $this->isArchived() ? static::$archiveClass : NULL );
}
/**
* Return the first comment on the item
*
* @return \IPS\Content\Comment|NULL
*/
public function firstComment()
{
if ( $this->isArchived() )
{
try
{
return parent::firstComment();
}
catch( \IPS\Db\Exception $e )
{
}
}
else
{
return parent::firstComment();
}
}
/**
* Check Moderator Permission
*
@@ -1853,18 +2017,18 @@ class _Topic extends \IPS\Content\Item implements
$member = $member ?: \IPS\Member::loggedIn();
/* Compatibility checks */
if ( in_array( $type, array( 'use_saved_actions', 'set_best_answer' ) ) )
if ( \in_array( $type, array( 'use_saved_actions', 'set_best_answer' ) ) )
{
$containerClass = get_class( $container );
$containerClass = \get_class( $container );
$title = static::$title;
if
(
$member->modPermission( $containerClass::$modPerm ) === -1
or
(
is_array( $member->modPermission( $containerClass::$modPerm ) )
\is_array( $member->modPermission( $containerClass::$modPerm ) )
and
in_array( $container->_id, $member->modPermission( $containerClass::$modPerm ) )
\in_array( $container->_id, $member->modPermission( $containerClass::$modPerm ) )
)
)
{
@@ -1875,52 +2039,28 @@ class _Topic extends \IPS\Content\Item implements
return parent::modPermission( $type, $member, $container );
}
/**
* Mark as read
*
* @param \IPS\Member|NULL $member The member (NULL for currently logged in member)
* @param int|NULL $time The timestamp to set (or NULL for current time)
* @param mixed $extraContainerWhere Additional where clause(s) (see \IPS\Db::build for details)
* @return void
*/
public function markRead( \IPS\Member $member = NULL, $time = NULL, $extraContainerWhere = NULL )
{
/**
* Mark as read
*
* @param \IPS\Member|NULL $member The member (NULL for currently logged in member)
* @param int|NULL $time The timestamp to set (or NULL for current time)
* @param mixed $extraContainerWhere Additional where clause(s) (see \IPS\Db::build for details)
* @param bool $force Mark as unread even if we already appear to be unread?
* @return void
*/
public function markRead( \IPS\Member $member = NULL, $time = NULL, $extraContainerWhere = NULL, $force = FALSE )
{
$member = $member ?: \IPS\Member::loggedIn();
$time = $time ?: time();
if ( !$this->container()->can_view_others and !$member->modPermission( 'can_read_all_topics' ) )
if ( !$this->container()->memberCanAccessOthersTopics( $member ) )
{
$extraContainerWhere = array( 'starter_id = ?', $member->member_id );
}
parent::markRead( $member, $time, $extraContainerWhere );
parent::markRead( $member, $time, $extraContainerWhere, $force );
}
/**
* Returns the meta description
*
* @param string|NULL $return Specific description to use (useful for paginated displays to prevent having to run extra queries)
* @return string
* @throws \BadMethodCallException
*/
public function metaDescription( $return = NULL )
{
/* We have to store and restore this for the topic controller */
if( isset( $_SESSION['_findComment'] ) )
{
$_findComment = $_SESSION['_findComment'];
}
$metaDescription = parent::metaDescription( $return );
if( isset( $_findComment ) )
{
$_SESSION['_findComment'] = $_findComment;
}
return $metaDescription;
}
/**
* Get output for API
*
@@ -1992,7 +2132,7 @@ class _Topic extends \IPS\Content\Item implements
*/
public static function supportedMetaDataTypes()
{
return array( 'core_FeaturedComments', 'core_ContentMessages' );
return array( 'core_FeaturedComments', 'core_ContentMessages', 'core_ItemModeration' );
}
/**
@@ -2006,4 +2146,123 @@ class _Topic extends \IPS\Content\Item implements
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'embed.css', 'forums', 'front' ) );
return \IPS\Theme::i()->getTemplate( 'global', 'forums' )->embedTopic( $this, $this->url()->setQueryString( $params ) );
}
/* ! Reactions */
public $reputation = array();
/**
* Reaction type
*
* @return string
*/
public static function reactionType()
{
return 'tid';
}
/**
* Reaction Where Clause
*
* @param \IPS\Content\Reaction|array|int|NULL $reactions This can be any one of the following: An \IPS\Content\Reaction object, an array of \IPS\Content\Reaction objects, an integer, or an array of integers, or NULL
* @param bool $enabledTypesOnly If TRUE, only reactions of the enabled reaction types will be included (must join core_reactions)
* @return array
*/
public function getReactionWhereClause( $reactions = NULL, $enabledTypesOnly=TRUE )
{
$idColumn = static::$databaseColumnId;
$class = static::reactionClass();
$where = array( array( 'rep_class=? and item_id=?', $class::$commentClass, $this->$idColumn ) );
if ( $enabledTypesOnly )
{
$where[] = array( 'reaction_enabled=1' );
}
if ( $reactions !== NULL )
{
if ( !\is_array( $reactions ) )
{
$reactions = array( $reactions );
}
$in = array();
foreach( $reactions AS $reaction )
{
if ( $reaction instanceof \IPS\Content\Reaction )
{
$in[] = $reaction->id;
}
else
{
$in[] = $reaction;
}
}
if ( \count( $in ) )
{
$where[] = array( \IPS\Db::i()->in( 'reaction', $in ) );
}
}
return $where;
}
/**
* Show the topic summary feature?
*
* @param $key string Key to check (topPost, popularDays, uploads)
* @return boolean
*/
public function showSummaryFeature( $key )
{
if ( \IPS\Settings::i()->forums_topic_activity_features )
{
$features = json_decode( \IPS\Settings::i()->forums_topic_activity_features, TRUE );
if ( $features and \in_array( $key, $features ) )
{
return TRUE;
}
}
return FALSE;
}
/**
* Show the topic summary on desktop? (if so where)
*
* @return string [sidebar,post]
*/
public function showSummaryOnDesktop()
{
if ( ! \IPS\Settings::i()->forums_topics_activity_pages_show OR ( (int) \IPS\Settings::i()->forums_topics_activity_pages_show <= (int) $this->commentPageCount() ) )
{
$viewSettings = json_decode( \IPS\Settings::i()->forums_topic_activity, TRUE );
if ( $viewSettings and \in_array( 'desktop', $viewSettings ) and isset( \IPS\Settings::i()->forums_topic_activity_desktop ) )
{
return \IPS\Settings::i()->forums_topic_activity_desktop;
}
}
return FALSE;
}
/**
* Show the topic summary on mobile?
*
* @return boolean
*/
public function showSummaryOnMobile()
{
if ( ! \IPS\Settings::i()->forums_topics_activity_pages_show OR ( (int) \IPS\Settings::i()->forums_topics_activity_pages_show <= (int) $this->commentPageCount() ) )
{
$viewSettings = json_decode( \IPS\Settings::i()->forums_topic_activity, TRUE );
if ( $viewSettings and \in_array( 'mobile', $viewSettings ) )
{
return TRUE;
}
}
return FALSE;
}
}