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

+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()
);
}
}