Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+23 -9
View File
@@ -2,10 +2,10 @@
/**
* @brief Feed Import Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Forums
* @since 4 Fed 2014
* @version SVN_VERSION_NUMBER
*/
@@ -107,7 +107,7 @@ class _Feed extends \IPS\Node\Model
{
if ( $enabled )
{
\IPS\DB::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 'rssimport' ) );
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 'rssimport' ) );
}
$this->enabled = $enabled;
}
@@ -254,6 +254,12 @@ class _Feed extends \IPS\Node\Model
*/
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();
@@ -269,11 +275,11 @@ class _Feed extends \IPS\Node\Model
$inserts = array();
$request = $request->decodeXml();
if( !in_array( $request->getname(), array( 'feed', 'rss' ) ) )
if( !( $request instanceof \IPS\Xml\RssOne ) AND !( $request instanceof \IPS\Xml\Rss ) AND !( $request instanceof \IPS\Xml\Atom ) )
{
throw new \RuntimeException( 'rss_import_invalid' );
}
foreach ( $request->articles( $this->id ) as $guid => $article )
{
if ( !in_array( $guid, $previouslyImportedGuids ) )
@@ -293,8 +299,16 @@ class _Feed extends \IPS\Node\Model
if ( $article['link'] and $this->showlink )
{
$rel = array();
$rel['nofollow'] = \IPS\Settings::i()->posts_add_nofollow ? "nofollow" : FALSE;
$rel['external'] = \IPS\Settings::i()->links_external ? "external" : FALSE;
if( \IPS\Settings::i()->posts_add_nofollow )
{
$rel['nofollow'] = 'nofollow';
}
if( \IPS\Settings::i()->links_external )
{
$rel['external'] = 'external';
}
$linkRelPart = '';
if ( count ( $rel ) )
@@ -306,7 +320,7 @@ class _Feed extends \IPS\Node\Model
}
$member = \IPS\Member::load( $this->mid );
$content = \IPS\Text\Parser::parseStatic( $article['content'] . $readMoreLink, FALSE, NULL, $member, 'forums_Forums', TRUE, !(bool) $member->group['g_dohtml'] );
$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;
+265 -38
View File
@@ -2,10 +2,10 @@
/**
* @brief Forum Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Forums
* @since 7 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -143,6 +143,20 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public static $icon = 'comments';
/**
* Callback from \IPS\Http\Url\Inernal::correctUrlFromVerifyClass()
*
* This is called when verifying the *the URL currently being viewed* is correct, before calling self::loadFromUrl()
* Can be used if there is a more effecient way to load and cache the objects that will be used later on that page
*
* @param \IPS\Http\Url $url The URL of the page being viewed, which belongs to this class
* @return void
*/
public static function preCorrectUrlFromVerifyClass( \IPS\Http\Url $url )
{
static::loadIntoMemory();
}
/**
* Form fields prefix with "forum_" but the database columns do not have this prefix - let's strip for the massChange feature
*
@@ -162,6 +176,24 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
parent::__set( $k, $v );
}
/**
* When setting parent ID to -1 (category) make sure sub_can_post is toggled off too
*
* @param int $val Parent ID
* @return void
*/
protected function set_parent_id( $val )
{
$this->_data['parent_id'] = $val;
$this->changed['parent_id'] = $val;
/* sub_can_post should get set to 0 for a category */
if( $val == -1 )
{
$this->sub_can_post = 0;
}
}
/**
* Get SEO name
*
@@ -171,11 +203,11 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if( !$this->_data['name_seo'] )
{
$this->name_seo = \IPS\Http\Url::seoTitle( \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'forums_forum_' . $this->id ) );
$this->name_seo = \IPS\Http\Url\Friendly::seoTitle( \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'forums_forum_' . $this->id ) );
$this->save();
}
return $this->_data['name_seo'] ?: \IPS\Http\Url::seoTitle( \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'forums_forum_' . $this->id ) );
return $this->_data['name_seo'] ?: \IPS\Http\Url\Friendly::seoTitle( \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( 'forums_forum_' . $this->id ) );
}
/**
@@ -292,13 +324,27 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
/* We prefer fetching post, joining topic, etc. but that is not efficient and causes a temp table and filesort against posts table so we'll lean on the cached last_post value for the topic */
$select = \IPS\Db::i()->select( '*', 'forums_topics', array( "forums_topics.forum_id=? AND forums_topics.approved=1 AND forums_topics.state != ?", $this->id, 'link' ), 'forums_topics.last_post DESC', 1 )->first();
$topic = \IPS\forums\Topic::constructFromData( $select );
if ( $topic->last_poster_id and ! $topic->last_poster_name )
{
$member = \IPS\Member::load( $topic->last_poster_id );
if ( $member->member_id )
{
$topic->last_poster_name = $member->name;
}
else
{
$topic->last_poster_name = '';
$topic->last_poster_id = 0;
}
}
$this->last_post = $topic->last_post;
$this->last_poster_id = (int) $topic->last_poster_id;
$this->last_poster_name = $topic->last_poster_name;
$this->seo_last_name = \IPS\Http\Url::seoTitle( $this->last_poster_name );
$this->seo_last_name = \IPS\Http\Url\Friendly::seoTitle( $this->last_poster_name );
$this->last_title = $topic->title;
$this->seo_last_title = \IPS\Http\Url::seoTitle( $this->last_title );
$this->seo_last_title = \IPS\Http\Url\Friendly::seoTitle( $this->last_title );
$this->last_id = $topic->tid;
return;
}
@@ -316,9 +362,9 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$this->last_post = $comment->mapped('date');
$this->last_poster_id = (int) $comment->author()->member_id;
$this->last_poster_name = $comment->author()->member_id ? $comment->author()->name : $comment->mapped('author_name');
$this->seo_last_name = \IPS\Http\Url::seoTitle( $this->last_poster_name );
$this->seo_last_name = \IPS\Http\Url\Friendly::seoTitle( $this->last_poster_name );
$this->last_title = $comment->item()->title;
$this->seo_last_title = \IPS\Http\Url::seoTitle( $this->last_title );
$this->seo_last_title = \IPS\Http\Url\Friendly::seoTitle( $this->last_title );
$this->last_id = $comment->item()->tid;
}
@@ -370,28 +416,41 @@ 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
(
!$this->can_view_others
and
(
!(
\IPS\Member::loggedIn()->modPermission( 'forums' ) === -1
or
(
is_array( \IPS\Member::loggedIn()->modPermission( 'forums' ) )
and
in_array( $this->_id, \IPS\Member::loggedIn()->modPermission( 'forums' ) )
)
)
and
!\IPS\Member::loggedIn()->modPermission( 'can_read_all_topics' )
)
)
elseif ( $fetchLastPostFromUser )
{
try
{
@@ -420,7 +479,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return $result;
}
elseif ( !$this->permission_showtopic and !$this->can('read') )
elseif ( !$this->permission_showtopic and !$this->can('view') )
{
$return = NULL;
@@ -475,7 +534,12 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$result = $childLastPost;
}
}
if ( $this->sub_can_post and !$this->permission_showtopic and !$this->can('read') AND !is_null( $result ) )
{
$result['topic_title'] = NULL;
}
return $result;
}
}
@@ -526,7 +590,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return TRUE;
}
if ( isset( \IPS\Request::i()->cookie[ 'ipbforumpass_' . $this->id ] ) and md5( $this->password ) === \IPS\Request::i()->cookie[ 'ipbforumpass_' . $this->id ] )
if ( isset( \IPS\Request::i()->cookie[ 'ipbforumpass_' . $this->id ] ) and \IPS\Login::compareHashes( md5( $this->password ), \IPS\Request::i()->cookie[ 'ipbforumpass_' . $this->id ] ) )
{
return TRUE;
}
@@ -642,7 +706,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$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 can_view_others=1)' );
}
}
@@ -815,10 +879,13 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
'forum_permission_custom_error',
"form_{$id}_header_permissions",
"form_{$id}_tab_forum_display",
'forum_allow_rating',
'forum_disable_sharelinks',
"form_{$id}_tab_posting_settings",
"form_{$id}_header_forum_display_topic",
'forum_preview_posts',
'forum_icon',
'forum_sort_key'
),
'qa' => array(
'forum_password_on',
@@ -830,6 +897,8 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
'forum_permission_custom_error',
"form_{$id}_header_permissions",
"form_{$id}_tab_forum_display",
'forum_allow_rating',
'forum_disable_sharelinks',
"form_{$id}_tab_posting_settings",
"form_{$id}_header_forum_display_question",
'forum_can_view_others_qa',
@@ -838,6 +907,12 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
'forum_qa_rate_answers',
'forum_preview_posts_qa',
'forum_icon',
'forum_sort_key_qa'
),
'category' => array(
"form_{$id}_tab_forum_display",
'forum_rules_title',
'forum_rules_text'
),
'redirect' => array(
'forum_password_on',
@@ -914,14 +989,19 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$form->add( new \IPS\Helpers\Form\Number( 'forum_min_posts_view', $this->id ? $this->min_posts_view : 0, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'no_restriction' ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('approved_posts_comments'), 'forum_min_posts_view' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_can_view_others', $this->id ? $this->can_view_others : TRUE, FALSE, array(), NULL, NULL, NULL, 'forum_can_view_others' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_can_view_others_qa', $this->id ? $this->can_view_others : TRUE, FALSE, array(), NULL, NULL, NULL, 'forum_can_view_others_qa' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_permission_showtopic', $this->permission_showtopic, FALSE, array(), NULL, NULL, NULL, 'forum_permission_showtopic' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_permission_showtopic_qa', $this->permission_showtopic, FALSE, array(), NULL, NULL, NULL, 'forum_permission_showtopic_qa' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_permission_showtopic', $this->permission_showtopic ?: 0, FALSE, array(), NULL, NULL, NULL, 'forum_permission_showtopic' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_permission_showtopic_qa', $this->permission_showtopic ?: 0, FALSE, array(), NULL, NULL, NULL, 'forum_permission_showtopic_qa' ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'forum_permission_custom_error', NULL, FALSE, array( 'app' => 'forums', 'key' => ( $this->id ? "forums_forum_{$this->id}_permerror" : NULL ), 'editor' => array( 'app' => 'forums', 'key' => 'Forums', 'autoSaveKey' => ( $this->id ? "forums-permerror-{$this->id}" : "forums-new-permerror" ), 'attachIds' => $this->id ? array( $this->id, NULL, 'permerror' ) : NULL, 'minimize' => 'forum_permerror_placeholder' ) ), NULL, NULL, NULL, 'forum_permission_custom_error' ) );
$form->addTab( 'forum_display' );
$form->addHeader( 'forum_display_forum' );
$sortOptions = array( 'last_post' => 'sort_updated', 'last_real_post' => 'sort_last_comment', 'posts' => 'sort_num_comments', 'views' => 'sort_views', 'title' => 'sort_title', 'starter_name' => 'sort_author_name', 'last_poster_name' => 'sort_last_comment_name', 'start_date' => 'sort_date' );
$sortOptionsQA = array( 'question_rating' => 'sort_question_rating' );
$form->add( new \IPS\Helpers\Form\Select( 'forum_sort_key', $this->id ? $this->sort_key : 'last_post', FALSE, array( 'options' => $sortOptions ), NULL, NULL, NULL, 'forum_sort_key' ) );
$form->add( new \IPS\Helpers\Form\Select( 'forum_sort_key_qa', $this->id ? $this->sort_key : 'last_post', FALSE, array( 'options' => array_merge( $sortOptions, $sortOptionsQA ) ), NULL, NULL, NULL, 'forum_sort_key_qa' ) );
$form->add( new \IPS\Helpers\Form\Select( 'forum_sort_key', $this->id ? $this->sort_key : 'last_post', FALSE, array( 'options' => array( 'last_post' => 'sort_updated', 'last_real_post' => 'sort_last_comment', 'posts' => 'sort_num_comments', 'views' => 'sort_views', 'title' => 'sort_title', 'starter_name' => 'sort_author_name', 'last_poster_name' => 'sort_last_comment_name', 'start_date' => 'sort_date' ) ), NULL, NULL, NULL, 'forum_sort_key' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'forum_show_rules', $this->id ? $this->show_rules : 0, FALSE, array(
'options' => array(
0 => 'forum_show_rules_none',
@@ -1033,6 +1113,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$values['forum_preview_posts'] = $values['forum_preview_posts_qa'];
$values['forum_can_view_others'] = $values['forum_can_view_others_qa'];
$values['forum_permission_showtopic'] = $values['forum_permission_showtopic_qa'];
$values['forum_sort_key'] = $values['forum_sort_key_qa'];
}
else
{
@@ -1048,6 +1129,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
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'] );
}
if ( isset( $values['forum_parent_id'] ) )
@@ -1101,6 +1183,13 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
$values['password'] = NULL;
}
/* Reset password and can view others if toggling back to a category */
if( in_array( $values['type'], array( 'category', 'redirect' ) ) )
{
$values['password'] = NULL;
$values['can_view_others'] = TRUE;
}
/* Reverse */
if( isset( $values['disable_sharelinks'] ) )
@@ -1136,13 +1225,13 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
foreach ( array( 'name' => "forums_forum_{$this->id}", 'description' => "forums_forum_{$this->id}_desc", 'rules_title' => "forums_forum_{$this->id}_rulestitle", 'rules_text' => "forums_forum_{$this->id}_rules", 'permission_custom_error' => "forums_forum_{$this->id}_permerror" ) as $fieldKey => $langKey )
{
if ( isset( $values[ $fieldKey ] ) )
if ( array_key_exists( $fieldKey, $values ) )
{
\IPS\Lang::saveCustom( 'forums', $langKey, $values[ $fieldKey ] );
if ( $fieldKey === 'name' )
{
$this->name_seo = \IPS\Http\Url::seoTitle( $values[ $fieldKey ][ \IPS\Lang::defaultLanguage() ] );
$this->name_seo = \IPS\Http\Url\Friendly::seoTitle( $values[ $fieldKey ][ \IPS\Lang::defaultLanguage() ] );
$this->save();
}
@@ -1158,6 +1247,13 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
unset( $values[ $k ] );
}
}
/* Update index */
if( $this->can_view_others !== NULL and isset( $values['can_view_others'] ) and $values['can_view_others'] != $this->can_view_others )
{
$this->can_view_others = $values['can_view_others'];
$this->updateSearchIndexPermissions();
}
return $values;
}
@@ -1217,6 +1313,13 @@ 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();
}
catch( \Exception $ex ) { }
parent::delete();
foreach ( array( 'rules_title' => "forums_forum_{$this->id}_rulestitle", 'rules_text' => "forums_forum_{$this->id}_rules", 'permission_custom_error' => "forums_forum_{$this->id}_permerror" ) as $fieldKey => $langKey )
@@ -1346,6 +1449,62 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return $result;
}
/**
* Get which permission keys can access all topics in a forum which
* can normally only show topics to the author
*
* @return array
*/
public function permissionsThatCanAccessAllTopics()
{
$normal = $this->searchIndexPermissions();
$return = array();
$members = array();
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $moderator )
{
if ( $moderator['perms'] === '*' or in_array( 'can_read_all_topics', explode( ',', $moderator['perms'] ) ) )
{
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 ) )
{
$return = array_merge( $return, $members );
}
return $return;
}
/**
* Update search index permissions
*
* @return void
*/
protected function updateSearchIndexPermissions()
{
if ( $this->can_view_others )
{
return parent::updateSearchIndexPermissions();
}
else
{
$permissions = implode( ',', $this->permissionsThatCanAccessAllTopics() );
\IPS\Content\Search\Index::i()->massUpdate( 'IPS\forums\Topic', $this->_id, NULL, $permissions, NULL, NULL, NULL, NULL, NULL, TRUE );
\IPS\Content\Search\Index::i()->massUpdate( 'IPS\forums\Topic\Post', $this->_id, NULL, $permissions, NULL, NULL, NULL, NULL, NULL, TRUE );
}
}
/**
* Mass move content items in this node to another node
*
@@ -1477,7 +1636,10 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Widget::deleteCaches( NULL, static::$permApp );
/* Log */
\IPS\Session::i()->modLog( 'modlog__action_massmove', array( $contentItemClass::$title . '_pl_lc' => TRUE, $node->url()->__toString() => FALSE, $node->_title => FALSE ) );
if ( \IPS\Dispatcher::hasInstance() )
{
\IPS\Session::i()->modLog( 'modlog__action_massmove', array( $contentItemClass::$title . '_pl_lc' => TRUE, $node->url()->__toString() => FALSE, $node->_title => FALSE ) );
}
return NULL;
}
@@ -1512,11 +1674,76 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$disabled = array();
$guestGroup = \IPS\Member\Group::load( \IPS\Settings::i()->guest_group );
if( !$this->can_view_others )
if( $this->sub_can_post and !$this->can_view_others )
{
$disabled[ $guestGroup->g_id ][] = 'view';
}
return $disabled;
}
/**
* The permission key or function used when building a node selector
* in search or stream functions.
*
* @return string|callable function
*/
public static function searchableNodesPermission()
{
return function( $node )
{
if ( $node->can( 'view' ) and $node->sub_can_post )
{
return TRUE;
}
return FALSE;
};
}
/**
* @brief Cached unsearchable node ids
*/
protected static $unsearchableNodeIds = FALSE;
/**
* Return either NULL for no restrictions, or a list of container IDs we cannot search in because of app specific permissions and configuration
* You do not need to check for 'view' permissions against the logged in member here. The Query search class does this for you.
* This method is intended for more complex set up items, like needing to have X posts to see a forum, etc.
* 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
*/
public static function unsearchableNodeIds()
{
if( static::$unsearchableNodeIds !== FALSE )
{
return static::$unsearchableNodeIds;
}
/* 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;
return static::$unsearchableNodeIds;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string name Forum name
* @apiresponse int topics Number of topics in forum
* @apiresponse string url URL
*/
public function apiOutput()
{
return array(
'id' => $this->id,
'name' => $this->_title,
'topics' => $this->topics,
'url' => (string) $this->url()
);
}
}
@@ -2,10 +2,10 @@
/**
* @brief Saved Action Node
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Forums
* @since 31 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -230,10 +230,6 @@ class _SavedAction extends \IPS\Node\Model
{
throw new \DomainException('DISABLED');
}
if ( $this->mm_forums !== '*' and !in_array( $topic->container()->_id, explode( ',', $this->mm_forums ) ) )
{
throw new \DomainException('BAD_FORUM');
}
if ( $this->mm_forums !== '*' and !in_array( $topic->container()->_id, explode( ',', $this->mm_forums ) ) )
{
throw new \DomainException('BAD_FORUM');
@@ -278,11 +274,11 @@ class _SavedAction extends \IPS\Node\Model
/* Hide/Unhide */
if ( $this->topic_approve == 1 )
{
$topic->unhide();
$topic->unhide( $member );
}
elseif ( $this->topic_approve == 2 )
{
$topic->hide();
$topic->hide( $member );
}
/* Reply */
@@ -2,10 +2,10 @@
/**
* @brief Archived Post Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Forums
* @since 24 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -28,6 +28,11 @@ class _ArchivedPost extends Post
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
/**
* @brief Title
*/
public static $archiveTitle = 'archivedpost';
/**
* @brief [ActiveRecord] Database Connection
+121 -37
View File
@@ -2,10 +2,10 @@
/**
* @brief Post Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Forums
* @since 8 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -116,41 +116,53 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
/**
* Create comment
*
* @param \IPS\Content\Item $item The content item just created
* @param string $comment The comment
* @param bool $first Is the first comment?
* @param string $guestName If author is a guest, the name to use
* @param \IPS\Content\Item $item The content item just created
* @param string $comment The comment
* @param bool $first Is the first comment?
* @param string $guestName If author is a guest, the name to use
* @param bool|NULL $incrementPostCount Increment post count? If NULL, will use static::incrementPostCount()
* @param \IPS\Member|NULL $member The author of this comment. If NULL, uses currently logged in member.
* @param \IPS\DateTime|NULL $time The time
* @param string|NULL $ipAddress The IP address or NULL to detect automatically
* @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @return static
*/
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL )
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() );
if ( !$comment->hidden() and $popularNowSettings = json_decode( \IPS\Settings::i()->forums_popular_now, TRUE ) and $popularNowSettings['posts'] and $popularNowSettings['minutes'] )
if ( !$comment->hidden() )
{
$popularNowInterval = new \DateInterval( 'PT' . $popularNowSettings['minutes'] . 'M' );
$comments = $item->comments( NULL, NULL, 'date', 'desc', NULL, FALSE, \IPS\DateTime::create()->sub( $popularNowInterval ) );
if ( count( $comments ) >= $popularNowSettings['posts'] )
{
$commentToBasePopularNowTimeOff = array_slice( $comments, ( $popularNowSettings['posts'] - 1 ), 1 );
$commentToBasePopularNowTimeOff = array_pop( $commentToBasePopularNowTimeOff );
$item->popular_time = \IPS\DateTime::ts( $commentToBasePopularNowTimeOff->post_date )->add( $popularNowInterval )->getTimestamp();
$item->save();
}
elseif ( $item->popular_time !== NULL )
{
$item->popular_time = NULL;
$item->save();
}
$item->rebuildPopularTime();
}
return $comment;
}
/**
* Syncing to run when hiding
*
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function onHide( $member )
{
parent::onHide( $member );
$this->item()->rebuildPopularTime();
}
/**
* Syncing to run when unhiding
*
* @param bool $approving If true, is being approved for the first time
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function onUnhide( $approving, $member )
{
parent::onUnhide( $approving, $member );
$this->item()->rebuildPopularTime();
}
/**
* Should posting this increment the poster's post count?
@@ -186,7 +198,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
* @param string $order MySQL ORDER BY clause (NULL to order by date)
* @param int|array $limit Limit clause
* @param string $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index
* @param bool|NULL $includeHiddenItems Include hidden files? Boolean or NULL to detect if currently logged member has permission
* @param mixed $includeHiddenItems Include hidden comments? NULL to detect if currently logged in member has permission, -1 to return public content only, TRUE to return unapproved content and FALSE to only return unapproved content the viewing member submitted
* @param int $queryFlags Select bitwise flags
* @param \IPS\Member $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
@@ -194,7 +206,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
* @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
*/
public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenItems=NULL, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL )
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 )
{
$where = \IPS\forums\Topic::getItemsWithPermissionWhere( $where, $permissionKey, $member, $joinContainer );
@@ -207,15 +219,17 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the author. Only includes columns returned by container::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @param bool $canIgnoreComments Can ignore comments in the result stream? Activity stream can, but search results cannot.
* @param array $template Optional custom template
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem )
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL )
{
/* Password protected */
if (
@@ -224,7 +238,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
and (
!isset( \IPS\Request::i()->cookie[ 'ipbforumpass_' . $indexData['index_container_id'] ] )
or
md5( $containerData['password'] ) !== \IPS\Request::i()->cookie[ 'ipbforumpass_' . $indexData['index_container_id'] ]
!\IPS\Login::compareHashes( md5( $containerData['password'] ), \IPS\Request::i()->cookie[ 'ipbforumpass_' . $indexData['index_container_id'] ] )
) // We don't have the correct password
)
{
@@ -233,9 +247,9 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
\IPS\Http\Url::internal( \IPS\forums\Forum::$urlBase . $indexData['index_container_id'], 'front', \IPS\forums\Forum::$urlTemplate, array( $containerData[ \IPS\forums\Forum::$databasePrefix . \IPS\forums\Forum::$seoTitleColumn ] ) )->setQueryString( 'topic', $indexData['index_item_id'] )
);
}
/* Minimum posts */
elseif ( $containerData['min_posts_view'] and $containerData['min_posts_view'] >= \IPS\Member::loggedIn()->member_po )
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'] ) ) ) );
}
@@ -243,7 +257,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
/* Normal */
else
{
return parent::searchResult( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $iPostedIn, $view, $asItem );
return parent::searchResult( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments, $template );
}
}
@@ -310,22 +324,56 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
*/
public function delete()
{
/* It is possible to delete a post that is orphaned, so let's try to protect against that */
try
{
$item = $this->item();
}
catch( \OutOfRangeException $e )
{
$item = NULL;
}
/* Reset best answer if relevant */
if ( $this->item()->topic_answered_pid == $this->pid )
if ( $item !== NULL AND $item->topic_answered_pid == $this->pid )
{
$this->item()->topic_answered_pid = FALSE;
$this->item()->save();
$item->topic_answered_pid = FALSE;
$item->save();
}
parent::delete();
/* Deleting a post may make the item no longer popular */
if( $item !== NULL )
{
$item->rebuildPopularTime();
}
}
/**
/**
* Indefinite Article
*
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @return string
*/
public function indefiniteArticle( \IPS\Lang $lang = NULL )
{
if ( $this->isFirst() AND $this->item()->container()->forums_bitoptions['bw_enable_answers'] )
{
$lang = $lang ?: \IPS\Member::loggedIn()->language();
return $lang->addToStack( '__indefart_question', FALSE );
}
return parent::indefiniteArticle( $lang );
}
/**
* Indefinite Article
*
* @param array $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang $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 )
{
$bitOptions = ( $containerData['forums_bitoptions'] instanceof \IPS\Patterns\Bitwise ) ? $containerData['forums_bitoptions'] : new \IPS\Patterns\Bitwise( array( 'forums_bitoptions' => $containerData['forums_bitoptions'] ), \IPS\forums\Forum::$bitOptions['forums_bitoptions'] );
@@ -340,6 +388,28 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
return parent::_indefiniteArticle( $containerData, $lang );
}
}
/**
* Definite Article
*
* @param array $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang $lang The language to use, or NULL for the language of the currently logged in member
* @return string
*/
public static function _definiteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL )
{
$bitOptions = ( $containerData['forums_bitoptions'] instanceof \IPS\Patterns\Bitwise ) ? $containerData['forums_bitoptions'] : new \IPS\Patterns\Bitwise( array( 'forums_bitoptions' => $containerData['forums_bitoptions'] ), \IPS\forums\Forum::$bitOptions['forums_bitoptions'] );
if ( $bitOptions['bw_enable_answers'] )
{
$lang = $lang ?: \IPS\Member::loggedIn()->language();
return $lang->addToStack( '__defart_answer', FALSE );
}
else
{
return parent::_definiteArticle( $containerData, $lang );
}
}
/**
* Get HTML
@@ -353,4 +423,18 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
return parent::html();
}
/**
* Force HTML posting abilities to TRUE for this comment
* This is usually determined by the member group and Editor extension.
* Here it can be overridden on a per comment basis
*
* @note Used currently in applications/core/extensions/core/Queue/RebuildPosts when rebuilding
*
* @return boolean
*/
public function htmlParsingEnforced()
{
return (boolean) $this->post_htmlstate > 0;
}
}
+317 -107
View File
@@ -2,10 +2,10 @@
/**
* @brief Topic Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Forums
* @since 8 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -144,6 +144,20 @@ class _Topic extends \IPS\Content\Item implements
* @brief Hover preview
*/
public $tableHoverUrl = TRUE;
/**
* Callback from \IPS\Http\Url\Inernal::correctUrlFromVerifyClass()
*
* This is called when verifying the *the URL currently being viewed* is correct, before calling self::loadFromUrl()
* Can be used if there is a more effecient way to load and cache the objects that will be used later on that page
*
* @param \IPS\Http\Url $url The URL of the page being viewed, which belongs to this class
* @return void
*/
public static function preCorrectUrlFromVerifyClass( \IPS\Http\Url $url )
{
\IPS\forums\Forum::loadIntoMemory();
}
/**
* Check the request for legacy parameters we may need to redirect to
@@ -168,6 +182,7 @@ class _Topic extends \IPS\Content\Item implements
/* p=123 needs to go to comment=123 */
if( isset( \IPS\Request::i()->p ) )
{
$paramsToSet['do'] = 'findComment';
$paramsToSet['comment'] = \IPS\Request::i()->p;
$paramsToUnset[] = 'p';
}
@@ -233,13 +248,13 @@ class _Topic extends \IPS\Content\Item implements
}
/**
* Get items with permisison check
* Get items with permission check
*
* @param array $where Where clause
* @param string $order MySQL ORDER BY clause (NULL to order by date)
* @param int|array $limit Limit clause
* @param string|NULL $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index or NULL to ignore permissions
* @param bool|NULL $includeHiddenItems Include hidden files? Boolean or NULL to detect if currently logged member has permission
* @param mixed $includeHiddenItems Include hidden items? NULL to detect if currently logged in member has permission, -1 to return public content only, TRUE to return unapproved content and FALSE to only return unapproved content the viewing member submitted
* @param int $queryFlags Select bitwise flags
* @param \IPS\Member $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
@@ -251,12 +266,13 @@ class _Topic extends \IPS\Content\Item implements
* @param bool $joinTags If true, will join the tags table
* @param bool $joinAuthor If true, will join the members table for the author
* @param bool $joinLastCommenter If true, will join the members table for the last commenter
* @param bool $showMovedLinks If true, moved item links are included in the results
* @return \IPS\Patterns\ActiveRecordIterator|int
*/
public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenItems=NULL, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL, $skipPermission=FALSE, $joinTags=TRUE, $joinAuthor=TRUE, $joinLastCommenter=TRUE )
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, $skipPermission=FALSE, $joinTags=TRUE, $joinAuthor=TRUE, $joinLastCommenter=TRUE, $showMovedLinks=FALSE )
{
$where = static::getItemsWithPermissionWhere( $where, $permissionKey, $member, $joinContainer, $skipPermission );
return parent::getItemsWithPermission( $where, $order, $limit, $permissionKey, $includeHiddenItems, $queryFlags, $member, $joinContainer, $joinComments, $joinReviews, $countOnly, $joins, $skipPermission, $joinTags, $joinAuthor, $joinLastCommenter );
return parent::getItemsWithPermission( $where, $order, $limit, $permissionKey, $includeHiddenItems, $queryFlags, $member, $joinContainer, $joinComments, $joinReviews, $countOnly, $joins, $skipPermission, $joinTags, $joinAuthor, $joinLastCommenter, $showMovedLinks );
}
/**
@@ -279,7 +295,7 @@ class _Topic extends \IPS\Content\Item implements
* @param string $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index
* @param \IPS\Member $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
* @param mixed $skipPermission If you are getting records from a specific container, pass the container to reduce the number of permission checks necessary or pass TRUE to skip conatiner-based permission. You must still specify this in the $where clause
* @param mixed $skipPermission If you are getting records from a specific container, pass the container to reduce the number of permission checks necessary or pass TRUE to skip container-based permission. You must still specify this in the $where clause
* @return array
*/
public static function getItemsWithPermissionWhere( $where, $permissionKey, $member, &$joinContainer, $skipPermission=FALSE )
@@ -304,18 +320,16 @@ class _Topic extends \IPS\Content\Item implements
}
}
}
/* Don't show links to moved topics */
$where['item'][] = array( "( NULLIF(forums_topics.moved_to, '') IS NULL )" );
$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' ) ) )
{
$member = $member ?: \IPS\Member::loggedIn();
if ( !$member->modPermission( 'can_read_all_topics' ) or $member->modPermission( 'forums' ) !== -1 )
if ( !$member->modPermission( 'can_read_all_topics' ) or ( $member->modPermission( 'forums' ) !== TRUE and $member->modPermission( 'forums' ) !== -1 ) )
{
if ( $skipPermission instanceof \IPS\forums\Forum )
{
@@ -343,7 +357,7 @@ class _Topic extends \IPS\Content\Item implements
}
}
}
/* 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' ) ) )
{
@@ -496,17 +510,11 @@ class _Topic extends \IPS\Content\Item implements
/* Moderator actions */
if ( isset( $values['topic_create_state'] ) )
{
if ( static::modPermission( 'feature', NULL, $this->container() ) )
if ( static::modPermission( 'lock', NULL, $this->container() ) )
{
$this->state = ( in_array( 'lock', $values['topic_create_state'] ) ) ? 'closed' : 'open';
}
if ( static::modPermission( 'hide', NULL, $this->container() ) )
{
$this->approved = ( in_array( 'hide', $values['topic_create_state'] ) ) ? -1 : 1;
}
if ( static::modPermission( 'pin', NULL, $this->container() ) )
{
$this->pinned = ( in_array( 'pin', $values['topic_create_state'] ) ) ? 1 : 0;
@@ -521,17 +529,11 @@ class _Topic extends \IPS\Content\Item implements
if ( static::modPermission( 'lock', NULL, $this->container() ) )
{
/* Set open/close time */
if ( isset( $values['topic_open_time'] ) and $values['topic_open_time'] )
$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'] ) )
{
$this->topic_open_time = $values['topic_open_time']->getTimestamp();
}
if ( isset( $values['topic_close_time'] ) and $values['topic_close_time'] )
{
$this->topic_close_time = $values['topic_close_time']->getTimestamp();
}
else if( isset( $values['topic_create_state'] ) and !in_array( 'lock', $values['topic_create_state'] ) )
{
$this->topic_close_time = 0;
$this->state = 'open';
}
@@ -541,8 +543,20 @@ class _Topic extends \IPS\Content\Item implements
$this->state = 'closed';
}
}
}
/**
* Process created object AFTER the object has been created
*
* @param \IPS\Content\Comment|NULL $comment The first comment
* @param array $values Values from form
* @return void
*/
protected function processAfterCreate( $comment, $values )
{
$this->processAfterCreateOrEdit( $values );
parent::processAfterCreate( $comment, $values );
}
/**
@@ -553,11 +567,13 @@ class _Topic extends \IPS\Content\Item implements
*/
public function processAfterEdit( $values )
{
$this->processAfterCreateOrEdit( $values );
/* Initial Comment */
parent::processAfterEdit( $values );
/* Topic changed? */
if ( ! $this->hidden() and ( $this->tid === $this->container()->last_id ) and ( $this->title_seo !== $this->container()->seo_last_title ) )
if ( ! $this->hidden() and ( $this->tid === $this->container()->last_id ) )
{
$this->container()->seo_last_title = $this->title_seo;
$this->container()->last_title = $this->title;
@@ -574,6 +590,32 @@ class _Topic extends \IPS\Content\Item implements
}
}
}
/**
* Process after the object has been edited or created on the front-end
*
* @param array $values Values from form
* @return void
*/
protected function processAfterCreateOrEdit( $values )
{
/* Moderator actions */
if ( isset( $values['topic_create_state'] ) )
{
if ( static::modPermission( 'hide', NULL, $this->container() ) )
{
if( in_array( 'hide', $values['topic_create_state'] ) )
{
$this->hide( NULL );
}
/* Only unhide if the topic is not pending approval */
elseif( $this->hidden() !== 1 )
{
$this->unhide( NULL );
}
}
}
}
/**
* @brief Cached URLs
@@ -633,6 +675,35 @@ class _Topic extends \IPS\Content\Item implements
return $stats;
}
/**
* Set the new popular time if needed
*
*/
public function rebuildPopularTime( )
{
$popularNowSettings = json_decode( \IPS\Settings::i()->forums_popular_now, TRUE );
if ( $popularNowSettings['posts'] and $popularNowSettings['minutes'] )
{
$popularNowInterval = new \DateInterval( 'PT' . $popularNowSettings['minutes'] . 'M' );
$comments = $this->comments( NULL, NULL, 'date', 'desc', NULL, FALSE, \IPS\DateTime::create()->sub( $popularNowInterval ) );
if ( count( $comments ) >= $popularNowSettings['posts'] )
{
$commentToBasePopularNowTimeOff = array_slice( $comments, ( $popularNowSettings['posts'] - 1 ), 1 );
$commentToBasePopularNowTimeOff = array_pop( $commentToBasePopularNowTimeOff );
$this->popular_time = \IPS\DateTime::ts( $commentToBasePopularNowTimeOff->post_date )->add( $popularNowInterval )->getTimestamp();
$this->save();
}
elseif ( $this->popular_time !== NULL )
{
$this->popular_time = NULL;
$this->save();
}
}
}
/**
* Set name
@@ -643,7 +714,7 @@ class _Topic extends \IPS\Content\Item implements
public function set_title( $title )
{
$this->_data['title'] = $title;
$this->_data['title_seo'] = \IPS\Http\Url::seoTitle( $title );
$this->_data['title_seo'] = \IPS\Http\Url\Friendly::seoTitle( $title );
}
/**
@@ -655,11 +726,11 @@ class _Topic extends \IPS\Content\Item implements
{
if( !$this->_data['title_seo'] )
{
$this->title_seo = \IPS\Http\Url::seoTitle( $this->title );
$this->title_seo = \IPS\Http\Url\Friendly::seoTitle( $this->title );
$this->save();
}
return $this->_data['title_seo'] ?: \IPS\Http\Url::seoTitle( $this->title );
return $this->_data['title_seo'] ?: \IPS\Http\Url\Friendly::seoTitle( $this->title );
}
/**
@@ -722,9 +793,9 @@ class _Topic extends \IPS\Content\Item implements
*/
public function searchIndexPermissions()
{
$return = parent::searchIndexPermissions();
$return = $this->container()->searchIndexPermissions();
if ( !$this->container()->can_view_others )
if ( $this->starter_id and !$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 )
@@ -732,40 +803,16 @@ class _Topic extends \IPS\Content\Item implements
return $return;
}
$normal = $return;
$return = array();
$members = array();
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $moderator )
{
if ( $moderator['perms'] === '*' or in_array( 'can_read_all_topics', explode( ',', $moderator['perms'] ) ) )
{
if( $moderator['type'] === 'g' )
{
$return[] = $moderator['id'];
}
else
{
$members[] = "m{$moderator['id']}";
}
}
}
$return = ( $normal == '*' ) ? array_unique( $return ) : array_intersect( explode( ',', $normal ), array_unique( $return ) );
$return = $this->container()->permissionsThatCanAccessAllTopics();
if ( $this->starter_id )
{
$members[] = "m{$this->starter_id}";
}
if( count( $members ) )
{
$return = array_merge( $return, $members );
$return[] = "m{$this->starter_id}";
}
$return = implode( ',', $return );
}
return $return;
}
@@ -855,11 +902,11 @@ class _Topic extends \IPS\Content\Item implements
if ( !empty( $openIds ) )
{
\IPS\Db::i()->update( 'forums_topics', array( 'state' => 'open', 'topic_close_time' => 0 ), \IPS\Db::i()->in( 'tid', $openIds ) );
\IPS\Db::i()->update( 'forums_topics', array( 'state' => 'open', 'topic_open_time' => 0 ), \IPS\Db::i()->in( 'tid', $openIds ) );
}
if ( !empty( $closeIds ) )
{
\IPS\Db::i()->update( 'forums_topics', array( 'state' => 'closed', 'topic_open_time' => 0 ), \IPS\Db::i()->in( 'tid', $closeIds ) );
\IPS\Db::i()->update( 'forums_topics', array( 'state' => 'closed', 'topic_close_time' => 0 ), \IPS\Db::i()->in( 'tid', $closeIds ) );
}
}
@@ -922,12 +969,123 @@ class _Topic extends \IPS\Content\Item implements
public function delete()
{
parent::delete();
\IPS\forums\Topic\ArchivedPost::db()->delete( 'forums_archive_posts', array( 'archive_topic_id=?', $this->tid ) );
try
{
\IPS\forums\Topic\ArchivedPost::db()->delete( 'forums_archive_posts', array( 'archive_topic_id=?', $this->tid ) );
}
/* catch db exceptions if e.g. if the connection credentials didn't work or if the database doesn't exist anymore */
catch ( \IPS\Db\Exception $e ){}
\IPS\Db::i()->delete( 'forums_question_ratings', array( 'topic=?', $this->tid ) );
\IPS\Db::i()->delete( 'forums_answer_ratings', array( 'topic=?', $this->tid ) );
/* Delete any moved topic links that point to this topic */
\IPS\Db::i()->delete( 'forums_topics', array( "state=? AND moved_to LIKE CONCAT( ?, '%' )", 'link', $this->tid . '&' ) );
}
/**
* Merge other items in (they will be deleted, this will be kept)
*
* @param array $items Items to merge in
* @param bool $keepLinks Retain redirect links for the items that were merge in
* @return void
*/
public function mergeIn( array $items, $keepLinks=FALSE )
{
/* If this is a QA forum we need to make sure we only have one best answer (at most) post-merge */
if( $this->isQuestion() )
{
/* Does this topic already have a best answer? */
if( $this->topic_answered_pid )
{
/* Then we need to make sure none of the items also has a best answer */
foreach( $items as $item )
{
/* Reset best answer for this topic */
if( $item->topic_answered_pid )
{
try
{
$post = \IPS\forums\Topic\Post::load( $item->topic_answered_pid );
$post->post_bwoptions['best_answer'] = FALSE;
$post->save();
}
catch( \OutOfRangeException $e ){}
}
}
}
/* The topic doesn't have a best answer, but we still need to make sure we only have one best answer total post-merge */
else
{
$bestAnswerSeen = FALSE;
foreach( $items as $item )
{
if( $item->topic_answered_pid )
{
/* Have we seen a best answer yet? If not, then we're ok. */
if( $bestAnswerSeen === FALSE )
{
/* This topic had no best answer flag set, so set it now */
$this->topic_answered_pid = $item->topic_answered_pid;
$this->save();
$bestAnswerSeen = TRUE;
continue;
}
/* If we have though, reset any others */
try
{
$post = \IPS\forums\Topic\Post::load( $item->topic_answered_pid );
$post->post_bwoptions['best_answer'] = FALSE;
$post->save();
}
catch( \OutOfRangeException $e ){}
}
}
}
}
return parent::mergeIn( $items, $keepLinks );
}
/**
* Hide
*
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @param string $reason Reason
* @return void
*/
public function hide( $member, $reason = NULL )
{
/* Hide any moved topic links that point to this topic */
foreach( \IPS\Db::i()->select( '*', 'forums_topics', array( "state=? AND moved_to LIKE CONCAT( ?, '%' )", 'link', $this->tid . '&' ) ) as $link )
{
\IPS\forums\Topic::constructFromData( $link )->hide( $member, $reason );
}
return parent::hide( $member, $reason );
}
/**
* Unhide
*
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function unhide( $member )
{
/* Unhide any moved topic links that point to this topic */
foreach( \IPS\Db::i()->select( '*', 'forums_topics', array( "state=? AND moved_to LIKE CONCAT( ?, '%' )", 'link', $this->tid . '&' ) ) as $link )
{
\IPS\forums\Topic::constructFromData( $link )->unhide( $member );
}
return parent::unhide( $member );
}
/* !Saved Actions */
/**
@@ -1052,14 +1210,14 @@ class _Topic extends \IPS\Content\Item implements
{
return TRUE;
}
/* Or if we're a moderator */
if
(
$member->modPermission( 'can_set_best_answer' )
and
(
$member->modPermission( \IPS\forums\Forum::$modPerm ) === -1
( $member->modPermission( \IPS\forums\Forum::$modPerm ) === TRUE or $member->modPermission( \IPS\forums\Forum::$modPerm ) === -1 )
or
(
is_array( $member->modPermission( \IPS\forums\Forum::$modPerm ) )
@@ -1217,6 +1375,27 @@ class _Topic extends \IPS\Content\Item implements
}
}
/**
* Definite Article
*
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @return string
*/
public static function _definiteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL )
{
$bitOptions = ( $containerData['forums_bitoptions'] instanceof \IPS\Patterns\Bitwise ) ? $containerData['forums_bitoptions'] : new \IPS\Patterns\Bitwise( array( 'forums_bitoptions' => $containerData['forums_bitoptions'] ), \IPS\forums\Forum::$bitOptions['forums_bitoptions'] );
if ( $bitOptions['bw_enable_answers'] )
{
$lang = $lang ?: \IPS\Member::loggedIn()->language();
return $lang->addToStack( '__defart_question', FALSE );
}
else
{
return parent::_definiteArticle( $containerData, $lang );
}
}
/* !Sitemap */
/**
@@ -1327,38 +1506,6 @@ class _Topic extends \IPS\Content\Item implements
return parent::canEdit( $member );
}
/**
* Can pin?
*
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
* @return bool
*/
public function canPin( $member=NULL )
{
if ( $this->isArchived() )
{
return FALSE;
}
return parent::canPin( $member );
}
/**
* Can unpin?
*
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
* @return bool
*/
public function canUnpin( $member=NULL )
{
if ( $this->isArchived() )
{
return FALSE;
}
return parent::canUnpin( $member );
}
/**
* Can feature?
*
@@ -1663,4 +1810,67 @@ class _Topic extends \IPS\Content\Item implements
return $metaDescription;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string title Title
* @apiresponse \IPS\forums\Forum forum Forum
* @apiresponse int posts Number of posts
* @apiresponse int views Number of posts
* @apiresponse string prefix The prefix tag, if there is one
* @apiresponse [string] tags The tags
* @apiresponse \IPS\forums\Topic\Post firstPost The first post in the topic
* @apiresponse \IPS\forums\Topic\Post lastPost The last post in the topic
* @apiresponse \IPS\forums\Topic\Post bestAnswer The best answer, if this is a question and there is one
* @apiresponse bool locked Topic is locked
* @apiresponse bool hidden Topic is hidden
* @apiresponse bool pinned Topic is pinned
* @apiresponse bool featured Topic is featured
* @apiresponse bool archived Topic is archived
* @apiresponse \IPS\Poll poll Poll data, if there is one
* @apiresponse string url URL
* @apiresponse float rating Average Rating
*/
public function apiOutput()
{
$firstPost = $this->comments( 1, 0, 'date', 'asc' );
$lastPost = $this->comments( 1, 0, 'date', 'desc' );
$bestAnswer = $this->bestAnswer();
return array(
'id' => $this->tid,
'title' => $this->title,
'forum' => $this->container()->apiOutput(),
'posts' => $this->posts,
'views' => $this->views,
'prefix' => $this->prefix(),
'tags' => $this->tags(),
'firstPost' => $firstPost ? $firstPost->apiOutput() : null,
'lastPost' => $lastPost ? $lastPost->apiOutput() : null,
'bestAnswer' => $bestAnswer ? $bestAnswer->apiOutput() : null,
'locked' => (bool) $this->locked(),
'hidden' => (bool) $this->hidden(),
'pinned' => (bool) $this->mapped('pinned'),
'featured' => (bool) $this->mapped('featured'),
'archived' => (bool) $this->isArchived(),
'poll' => $this->poll_state ? \IPS\Poll::load( $this->poll_state )->apiOutput() : null,
'url' => (string) $this->url(),
'rating' => $this->averageRating(),
);
}
/**
* Returns the content
*
* @return string
* @throws \BadMethodCallException
* @note This is overridden for performance reasons - selecting a post by a PID is more efficient than select * from posts order by date desc limit 1
*/
public function content()
{
$firstComment = $this->firstComment();
return $firstComment ? $firstComment->content() : '';
}
}