Version 4.7.6

This commit is contained in:
Neo committed 2025-12-19 16:05:19 -08:00
1 parent 8ba7d43898
commit 7b7404e741
1085 files changed
+30590 -7837

No files matched your search

+90 -21
View File
@@ -343,11 +343,14 @@ 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
* We also need to fetch from the write server in the event that something has just been deleted and the comment hasn't been passed
* We also need to fetch from the write server in the event that something has just been deleted and the comment hasn't been passed.
* We also cannot look for future entries since this code is used in older upgrades, but we can look for the date instead.
*/
$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, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
$select = \IPS\Db::i()->select( '*', 'forums_topics', array( "forums_topics.forum_id=? AND forums_topics.approved=1 AND forums_topics.state != ? AND forums_topics.last_post<=?", $this->id, 'link', time() ), 'forums_topics.last_post DESC', 1, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->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 );
@@ -436,7 +439,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
try
{
$lastPost = \IPS\forums\Topic\Post::constructFromData( \IPS\Db::i()->select( '*', 'forums_posts', array( 'topic_id=? AND queued=0', \IPS\Db::i()->select( 'tid', 'forums_topics', array( 'forum_id=? AND approved=1 AND starter_id=?', $this->_id, \IPS\Member::loggedIn()->member_id ), 'last_post DESC', 1 )->first() ), 'post_date DESC', 1 )->first() );
$lastPost = \IPS\forums\Topic\Post::constructFromData( \IPS\Db::i()->select( '*', 'forums_posts', array( 'topic_id=? AND queued=0', \IPS\Db::i()->select( 'tid', 'forums_topics', array( 'forum_id=? AND approved=1 AND starter_id=? AND is_future_entry=0', $this->_id, \IPS\Member::loggedIn()->member_id ), 'last_post DESC', 1 )->first() ), 'post_date DESC', 1 )->first() );
$result = array(
'author' => $lastPost->author(),
'topic_url' => $lastPost->item()->url(),
@@ -663,11 +666,6 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
}
catch ( \Exception $e ) { }
}
if ( !$this->viglink )
{
\IPS\Settings::i()->viglink_enabled = FALSE;
}
}
/**
@@ -883,7 +881,6 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
'normal' => array( // make sure when adding here that you also add to qa below
'forum_password_on',
'forum_ipseo_priority',
'forum_viglink',
'forum_min_posts_view',
'forum_can_view_others',
'forum_permission_showtopic',
@@ -903,7 +900,6 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
'qa' => array(
'forum_password_on',
'forum_ipseo_priority',
'forum_viglink',
'forum_min_posts_view',
'forum_can_view_others_qa',
'forum_permission_showtopic_qa',
@@ -1010,11 +1006,6 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
)
), NULL, NULL, NULL, 'forum_ipseo_priority' ) );
if ( \IPS\Settings::i()->viglink_enabled )
{
$form->add( new \IPS\Helpers\Form\YesNo( 'forum_viglink', $this->id ? $this->viglink : TRUE, FALSE, array(), NULL, NULL, NULL, 'forum_viglink' ) );
}
$form->addHeader( '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' ) );
@@ -1074,7 +1065,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$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');
@@ -1213,8 +1204,18 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if( isset( $values[ $k ] ) )
{
$values['forums_bitoptions'][ $k ] = ( $k == 'bw_enable_answers_member' or $k == 'bw_enable_answers_moderator' or $k == 'bw_fluid_view' ) ? $values[ $k ] : !$values[ $k ];
unset( $values[ $k ] );
/* If were disabling bw_enable_answers_moderator for discussion forums, we need to make sure bw_enable_answers_member is also disabled */
if ( $k == 'bw_enable_answers_moderator' AND !$values[ $k ] )
{
$values['forums_bitoptions']['bw_enable_answers_moderator'] = FALSE;
$values['forums_bitoptions']['bw_enable_answers_member'] = FALSE;
unset( $values['bw_enable_answers_moderator'], $values['bw_enable_answers_member'] );
}
else
{
$values['forums_bitoptions'][ $k ] = ( $k == 'bw_enable_answers_member' or $k == 'bw_enable_answers_moderator' or $k == 'bw_fluid_view' ) ? $values[ $k ] : !$values[ $k ];
unset( $values[ $k ] );
}
}
}
@@ -1281,6 +1282,8 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$values['preview_posts'] = 0;
}
}
$values['min_posts_post'] = (int) $values['min_posts_post'];
/* Feature color */
if ( isset( $values['use_feature_color'] ) )
@@ -1889,6 +1892,43 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return $return;
}
/**
* Return the time the first topic was marked as solved in this forum
*
* @return int
*/
public function getFirstSolvedTime(): int
{
if ( ! $this->solved_stats_from )
{
try
{
$post = \IPS\Db::i()->select( '*', 'forums_posts', [
'pid IN (?)', \IPS\Db::i()->select( 'topic_answered_pid', 'forums_topics', ['forum_id=?', $this->_id] )
], 'post_date ASC', 1 )->first();
try
{
$this->solved_stats_from = \IPS\Db::i()->select( 'solved_date', 'core_solved_index', [ 'app=? and comment_id=?', 'forums', $post['pid'] ] )->first();
}
catch( \Exception $e )
{
/* If that goes wrong... */
$this->solved_stats_from = $post['post_date'];
}
}
catch( \UnderflowException $e )
{
/* If all that goes wrong, we have nothing marked so just store now to prevent it rebuilding over and over */
$this->solved_stats_from = time();
}
$this->save();
}
return $this->solved_stats_from;
}
/* !Clubs */
@@ -2016,7 +2056,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$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 )
if ( ! $chooseable or !\IPS\Member::loggedIn()->member_id )
{
return \IPS\Settings::i()->forums_view_list_method;
}
@@ -2049,7 +2089,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$method = ( isset( \IPS\Request::i()->cookie['forum_view'] ) ) ? \IPS\Request::i()->cookie['forum_view'] : NULL;
$chooseable = \IPS\Settings::i()->forums_default_view_choose ? json_decode( \IPS\Settings::i()->forums_default_view_choose , TRUE ) : FALSE;
if ( ! $chooseable )
if ( ! $chooseable or !\IPS\Member::loggedIn()->member_id )
{
return \IPS\Settings::i()->forums_default_view;
}
@@ -2172,4 +2212,33 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return $type;
}
/**
* Content was held for approval by container
* Allow node classes that can determine if content should be held for approval in individual nodes
*
* @param string $content The type of content we are checking (item, comment, review).
* @param \IPS\Member|NULL $member Member to check or NULL for currently logged in member.
* @return bool
*/
public function contentHeldForApprovalByNode( string $content, ?\IPS\Member $member = NULL ): bool
{
/* If members group bypasses, then no. */
$member = $member ?: \IPS\Member::loggedIn();
if ( $member->group['g_avoid_q'] )
{
return FALSE;
}
switch( $content )
{
case 'item':
return (bool) ( \in_array( $this->preview_posts, array( 1, 2 ) ) );
break;
case 'comment':
return (bool) ( \In_array( $this->preview_posts, array( 1, 3 ) ) );
break;
}
}
}
+17 -2
View File
@@ -560,8 +560,23 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \I
public function webhookFilters()
{
$return = parent::webhookFilters();
$return['forums'] = $this->item()->container()->_id;
$return['hasBestAnswer'] = (bool) $this->item()->topic_answered_pid;
try
{
$return['forums'] = $this->item()->container()->_id;
$return['hasBestAnswer'] = (bool) $this->item()->topic_answered_pid;
}
catch( \OutOfRangeException $e ) {}
return $return;
}
/**
* Is this a future entry?
*
* @return bool
*/
public function isFutureDate()
{
return $this->item()->isFutureDate();
}
}
+8 -2
View File
@@ -34,7 +34,8 @@ class _Topic extends \IPS\Content\Item implements
\IPS\Content\Searchable,
\IPS\Content\Embeddable,
\IPS\Content\MetaData,
\IPS\Content\Anonymous
\IPS\Content\Anonymous,
\IPS\Content\FuturePublishing
{
use \IPS\Content\Reactable, \IPS\Content\Reportable, \IPS\Content\Statistics, \IPS\Content\Solvable { toggleSolveComment as protected _toggleSolveComment; }
@@ -123,6 +124,8 @@ class _Topic extends \IPS\Content\Item implements
'solved_comment_id' => 'topic_answered_pid',
'is_anon' => 'is_anon',
'last_comment_anon' => 'last_poster_anon',
'is_future_entry' => 'is_future_entry',
'future_date' => 'publish_date',
);
/**
@@ -302,9 +305,10 @@ class _Topic extends \IPS\Content\Item implements
* @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
* @param array|null $location Array of item lat and long
* @return \IPS\Patterns\ActiveRecordIterator|int
*/
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 )
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, $location=NULL )
{
$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, $showMovedLinks );
@@ -2176,6 +2180,8 @@ class _Topic extends \IPS\Content\Item implements
'poll' => $this->poll_state ? \IPS\Poll::load( $this->poll_state )->apiOutput( $authorizedMember ) : null,
'url' => (string) $this->url(),
'rating' => $this->averageRating(),
'is_future_entry' => $this->is_future_entry,
'publish_date' => $this->publish_date ? \IPS\DateTime::ts( $this->publish_date )->rfc3339() : NULL
);
}