Version 4.6.0

This commit is contained in:
Neo committed 2025-12-19 05:55:31 -08:00
1 parent f79dcf067a
commit 517a5e1f70
2036 files changed
+110041 -26162

No files matched your search

+360 -115
View File
@@ -346,13 +346,13 @@ abstract class _Item extends \IPS\Content
}
/* Increment post count */
if ( !$obj->hidden() and static::incrementPostCount( $container ) )
if ( !$obj->hidden() and static::incrementPostCount( $container ) and ! $obj->isAnonymous() )
{
$obj->author()->member_posts++;
}
/* Update member's last post */
if( $obj->author()->member_id AND $obj::incrementPostCount() )
if( $obj->author()->member_id AND $obj::incrementPostCount() AND ! $obj->isAnonymous() )
{
$obj->author()->member_last_post = time();
$obj->author()->save();
@@ -434,9 +434,16 @@ abstract class _Item extends \IPS\Content
$member->save();
}
/* Post anonymously */
if( isset( $values[ 'post_anonymously' ] ) )
{
$obj->setAnonymous( $values[ 'post_anonymously' ] );
}
/* Do any processing */
$obj->processAfterCreate( $comment, $values );
/* Auto-follow */
if( isset( $values[ static::$formLangPrefix . 'auto_follow'] ) AND $values[ static::$formLangPrefix . 'auto_follow'] )
{
@@ -494,6 +501,12 @@ abstract class _Item extends \IPS\Content
$obj->sendUnapprovedNotification();
}
}
/* Dish out points */
if ( !$obj->isFutureDate() and !$obj->hidden() )
{
$member->achievementAction( 'core', 'NewContentItem', $obj );
}
/* Return */
return $obj;
@@ -756,6 +769,12 @@ abstract class _Item extends \IPS\Content
$return['auto_follow'] = new \IPS\Helpers\Form\YesNo( static::$formLangPrefix . 'auto_follow', (bool) \IPS\Member::loggedIn()->auto_follow['content'], FALSE, array( 'label' => \IPS\Member::loggedIn()->language()->addToStack( static::$formLangPrefix . 'auto_follow_suffix' ) ), NULL, NULL, NULL, static::$formLangPrefix . 'auto_follow' );
}
/* Post Anonymously */
if ( $container and $container->canPostAnonymously( $container::ANON_ITEMS ) )
{
$return['post_anonymously'] = new \IPS\Helpers\Form\YesNo( 'post_anonymously', ( $item ) ? $item->isAnonymous() : FALSE , FALSE, array( 'label' => \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_suffix' ) ), NULL, NULL, NULL, 'post_anonymously' );
}
/* Share Links */
if ( $item === NULL and \in_array( 'IPS\Content\Shareable', class_implements( \get_called_class() ) ) )
{
@@ -1080,7 +1099,7 @@ abstract class _Item extends \IPS\Content
}
/* Check if profanity filters should mod-queue this comment */
$sendNotifications = $commentObj->checkProfanityFilters( TRUE, TRUE );
$sendNotifications = $commentObj->checkProfanityFilters( TRUE, TRUE, $values[ static::$formLangPrefix . 'content'] );
/* Send notifications */
if ( $sendNotifications AND !\in_array( 'IPS\Content\Review', class_parents( \get_called_class() ) ) )
@@ -1093,7 +1112,7 @@ abstract class _Item extends \IPS\Content
}
$oldValue = $commentObj->$column;
$commentObj->$column = $values[static::$formLangPrefix . 'content'];
$commentObj->$column = $values[ static::$formLangPrefix . 'content'];
$commentObj->save();
$commentObj->sendAfterEditNotifications( $oldValue );
@@ -1129,6 +1148,12 @@ abstract class _Item extends \IPS\Content
}
}
}
/* Post anonymously */
if( isset( $values[ 'post_anonymously' ] ) and ( isset( $container ) and $container->canPostAnonymously( $container::ANON_ITEMS ) ) )
{
$this->setAnonymous( $values[ 'post_anonymously' ] );
}
}
/* Holds the old content for edit logging */
@@ -1336,7 +1361,7 @@ abstract class _Item extends \IPS\Content
switch( $pref )
{
case 'unread':
$action = 'getNewComment';
$action = \IPS\Member::loggedIn()->member_id ? 'getNewComment' : NULL;
break;
case 'last':
@@ -1348,6 +1373,10 @@ abstract class _Item extends \IPS\Content
break;
}
}
elseif( !\IPS\Member::loggedIn()->member_id AND $action == 'getNewComment' )
{
$action = NULL;
}
if ( isset( static::$urlBase ) and isset( static::$urlTemplate ) and isset( static::$seoTitleColumn ) )
{
@@ -1407,7 +1436,7 @@ abstract class _Item extends \IPS\Content
switch( $pref )
{
case 'unread':
$action = 'getNewComment';
$action = \IPS\Member::loggedIn()->member_id ? 'getNewComment' : NULL;
break;
case 'last':
@@ -1419,6 +1448,10 @@ abstract class _Item extends \IPS\Content
break;
}
}
elseif( !\IPS\Member::loggedIn()->member_id AND $action == 'getNewComment' )
{
$action = NULL;
}
$url = \IPS\Http\Url::internal( static::$urlBase . $indexData['index_item_id'], 'front', static::$urlTemplate, \IPS\Http\Url\Friendly::seoTitle( $indexData['index_title'] ?: $itemData[ static::$databasePrefix . static::$databaseColumnMap['title'] ] ) );
@@ -1641,7 +1674,7 @@ abstract class _Item extends \IPS\Content
if ( $return )
{
$return = trim( str_replace( array( '"', "'", '&gt;', '&lt;' ), '', preg_replace( "/\s+/um", " ", str_replace( '&nbsp;', ' ', strip_tags( preg_replace('#(<(script|style)\b[^>]*>).*?(</\2>)#is', "$1$3", $return ) ) ) ) ) );
$return = trim( preg_replace( "/\s+/um", " ", str_replace( '&nbsp;', ' ', strip_tags( preg_replace('#(<(script|style)\b[^>]*>).*?(</\2>)#is', "$1$3", $return ) ) ) ) );
if ( mb_strlen( $return ) > 300 )
{
$return = mb_substr( $return, 0, 297 ) . '...';
@@ -1864,12 +1897,15 @@ abstract class _Item extends \IPS\Content
}
/* Rebuild node data */
$oldContainer->setLastComment();
$oldContainer->setLastReview();
$oldContainer->save();
$container->setLastComment();
$container->setLastReview();
$container->save();
if( !$this->skipContainerRebuild )
{
$oldContainer->setLastComment();
$oldContainer->setLastReview();
$oldContainer->save();
$container->setLastComment();
$container->setLastReview();
$container->save();
}
/* Add to search index */
if ( $this instanceof \IPS\Content\Searchable )
@@ -2063,6 +2099,9 @@ abstract class _Item extends \IPS\Content
\IPS\Db::i()->update( 'core_moderator_logs', array( 'item_id' => $this->$idColumn ), array( 'item_id=? AND class=?', $item->$idColumn, (string) \get_class( $this ) ) );
\IPS\Session::i()->modLog( 'modlog__action_merge', array( $item->mapped('title') => FALSE, $this->url()->__toString() => FALSE, $this->mapped('title') => FALSE ), $this );
/* Add to the redirect table */
$item->setRedirectTo( $this );
/* If we are adding redirects to the merged items, then we need to change these to link items. */
if ( $keepLinks AND isset( $item::$databaseColumnMap['moved_to'] ) )
{
@@ -2264,6 +2303,12 @@ abstract class _Item extends \IPS\Content
$this->hide( NULL );
}
}
/* Update mappings */
if ( isset( static::$databaseColumnMap['container'] ) and \IPS\IPS::classUsesTrait( $this->container(), 'IPS\Node\Statistics' ) )
{
$this->container()->rebuildPostedIn( array( $this->$idColumn ) );
}
}
/* Update last comment stuff */
@@ -2290,6 +2335,12 @@ abstract class _Item extends \IPS\Content
$this->container()->setLastReview();
$this->container()->save();
}
/* Clear cached statistics */
if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Statistics' ) )
{
$this->clearCachedStatistics();
}
/* Add to search index */
if ( $this instanceof \IPS\Content\Searchable )
@@ -2487,6 +2538,8 @@ abstract class _Item extends \IPS\Content
return;
}
}
\IPS\Db::i()->delete( 'core_item_member_map', array( 'map_class=? and map_item_id=?', (string) \get_class( $this ), (int) $this->$idColumn ) );
/* Remove any meta data */
if ( ( $this instanceof \IPS\Content\MetaData ) AND isset( static::$databaseColumnMap['meta_data'] ) )
@@ -2539,34 +2592,34 @@ abstract class _Item extends \IPS\Content
$commentIds[] = $commentId;
}
\IPS\Db::i()->delete( 'core_deletion_log', array( "dellog_content_class=? AND " . \IPS\Db::i()->in( 'dellog_content_id', $commentIds ), $commentClass ) );
\IPS\Db::i()->delete( 'core_social_promote', array( "promote_class=? AND " . \IPS\Db::i()->in( 'promote_class_id', $commentIds ), $commentClass ) );
\IPS\Db::i()->delete( 'core_reputation_index', array( "rep_class=? AND " . \IPS\Db::i()->in( 'type_id', $commentIds ), $commentClass ) );
\IPS\Db::i()->delete( 'core_solved_index', array( "comment_class=? AND " . \IPS\Db::i()->in( 'comment_id', $commentIds ), $commentClass ) );
$this->deleteCommentOrReviewData( $commentIds, $commentClass );
if ( \is_array( $where ) and \count( $where ) )
{
\IPS\Db::i()->delete( $commentClass::$databaseTable, $where );
}
try
if( !$this->skipContainerRebuild )
{
if ( $this->container()->_comments !== NULL )
try
{
/* We decrement the comment count onHide() */
if ( ! $this->hidden() )
if ( $this->container()->_comments !== NULL )
{
$this->container()->_comments = ( $this->container()->_comments - $this->mapped('num_comments') );
/* We decrement the comment count onHide() */
if ( ! $this->hidden() )
{
$this->container()->_comments = ( $this->container()->_comments - $this->mapped('num_comments') );
}
$this->container()->setLastComment();
}
$this->container()->setLastComment();
}
if ( $this->container()->_unapprovedComments !== NULL )
{
$this->container()->_unapprovedComments = ( $this->container()->_unapprovedComments - $this->mapped('unapproved_comments') );
}
$this->container()->save();
} catch ( \BadMethodCallException $e ) {}
if ( $this->container()->_unapprovedComments !== NULL )
{
$this->container()->_unapprovedComments = ( $this->container()->_unapprovedComments - $this->mapped('unapproved_comments') );
}
$this->container()->save();
} catch ( \BadMethodCallException $e ) {}
}
}
/* Delete reviews */
@@ -2587,32 +2640,32 @@ abstract class _Item extends \IPS\Content
{
$reviewIds[] = $reviewId;
}
\IPS\Db::i()->delete( 'core_deletion_log', array( "dellog_content_class=? AND " . \IPS\Db::i()->in( 'dellog_content_id', $reviewIds ), $reviewClass ) );
\IPS\Db::i()->delete( 'core_social_promote', array( "promote_class=? AND " . \IPS\Db::i()->in( 'promote_class_id', $reviewIds ), $reviewClass ) );
\IPS\Db::i()->delete( 'core_reputation_index', array( "rep_class=? AND " . \IPS\Db::i()->in( 'type_id', $reviewIds ), $reviewClass ) );
\IPS\Db::i()->delete( 'core_solved_index', array( "comment_class=? AND " . \IPS\Db::i()->in( 'comment_id', $reviewIds ), $reviewClass ) );
$this->deleteCommentOrReviewData( $reviewIds, $reviewClass );
\IPS\Db::i()->delete( $reviewClass::$databaseTable, $where );
try
if( !$this->skipContainerRebuild )
{
if ( $this->container()->_reviews !== NULL )
try
{
/* We decrement the review count onHide() */
if ( ! $this->hidden() )
if ( $this->container()->_reviews !== NULL )
{
$this->container()->_reviews = ( $this->container()->_reviews - $this->mapped('num_reviews') );
/* We decrement the review count onHide() */
if ( ! $this->hidden() )
{
$this->container()->_reviews = ( $this->container()->_reviews - $this->mapped('num_reviews') );
}
$this->container()->setLastReview();
}
$this->container()->setLastReview();
}
if ( $this->container()->_unapprovedReviews !== NULL )
{
$this->container()->_unapprovedReviews = ( $this->container()->_unapprovedReviews - $this->mapped('unapproved_reviews') );
}
$this->container()->save();
} catch ( \BadMethodCallException $e ) {}
if ( $this->container()->_unapprovedReviews !== NULL )
{
$this->container()->_unapprovedReviews = ( $this->container()->_unapprovedReviews - $this->mapped('unapproved_reviews') );
}
$this->container()->save();
} catch ( \BadMethodCallException $e ) {}
}
}
/* Delete tags */
@@ -2645,6 +2698,9 @@ abstract class _Item extends \IPS\Content
/* Delete from Our Picks */
\IPS\Db::i()->delete( 'core_social_promote', array( 'promote_class=? AND promote_class_id=?', (string) \get_class( $this ), (int) $this->$idColumn ) );
/* Delete from redirect links */
\IPS\Db::i()->delete( 'core_item_redirect', array( 'redirect_class=? AND redirect_new_item_id=?', (string) \get_class( $this ), (int) $this->$idColumn ) );
/* Delete Polls */
if ( $this instanceof \IPS\Content\Polls and $this->getPoll() )
{
@@ -2662,6 +2718,20 @@ abstract class _Item extends \IPS\Content
\IPS\Member::load( $member )->recountNotifications();
}
}
/**
* Deletes any additional comment Related data
*
* @param array $ids comment or review ids which are going to be deleted
* @param string $class comment or review class name
*/
protected function deleteCommentOrReviewData( array $ids, string $class )
{
\IPS\Db::i()->delete( 'core_deletion_log', array('dellog_content_class=? AND ' . \IPS\Db::i()->in( 'dellog_content_id', $ids ), $class) );
\IPS\Db::i()->delete( 'core_social_promote', array('promote_class=? AND ' . \IPS\Db::i()->in( 'promote_class_id', $ids ), $class) );
\IPS\Db::i()->delete( 'core_reputation_index', array('rep_class=? AND ' . \IPS\Db::i()->in( 'type_id', $ids ), $class) );
\IPS\Db::i()->delete( 'core_solved_index', array('comment_class=? AND ' . \IPS\Db::i()->in( 'comment_id', $ids ), $class) );
}
/**
* Change IP Address
@@ -2707,7 +2777,7 @@ abstract class _Item extends \IPS\Content
parent::changeAuthor( $newAuthor, $log );
/* Adjust post counts, but only if this is a visible post or the previous user was not a guest */
if ( static::incrementPostCount( $this->containerWrapper() ) AND ( $oldAuthor->member_id OR $this->hidden() === 0 ) )
if ( static::incrementPostCount( $this->containerWrapper() ) AND ( $oldAuthor->member_id OR $this->hidden() === 0 ) and ! $this->isAnonymous() )
{
if( $oldAuthor->member_id )
{
@@ -2885,8 +2955,8 @@ abstract class _Item extends \IPS\Content
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['approved'];
if ( $member->member_id and $includeHiddenItems !== \IPS\Content\Hideable::FILTER_PUBLIC_ONLY )
{
/* Only fetching a count, for a single container, no future publishing, and there is only one where clause (the container limitation) */
if( $countOnly === TRUE AND $skipPermission instanceof \IPS\Node\Model AND !\in_array( 'IPS\Content\FuturePublishing', class_implements( \get_called_class() ) ) AND \count( $where ) === 1 )
/* Only fetching a count, for a single container with a item cache count, no future publishing, and there is only one where clause (the container limitation) */
if( $countOnly === TRUE AND $skipPermission instanceof \IPS\Node\Model AND $skipPermission->_items !== NULL AND !\in_array( 'IPS\Content\FuturePublishing', class_implements( \get_called_class() ) ) AND \count( $where ) === 1 )
{
$countShortcut = TRUE;
$where[] = array( "({$col}=0 AND ( {$authorCol}={$member->member_id}{$extra} ) )" );
@@ -2906,8 +2976,8 @@ abstract class _Item extends \IPS\Content
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['hidden'];
if ( $member->member_id and $includeHiddenItems !== \IPS\Content\Hideable::FILTER_PUBLIC_ONLY )
{
/* Only fetching a count, for a single container, no future publishing, and there is only one where clause (the container limitation) */
if( $countOnly === TRUE AND $skipPermission instanceof \IPS\Node\Model AND !\in_array( 'IPS\Content\FuturePublishing', class_implements( \get_called_class() ) ) AND \count( $where ) === 1 )
/* Only fetching a count, for a single container with a item cache count, no future publishing, and there is only one where clause (the container limitation) */
if( $countOnly === TRUE AND $skipPermission instanceof \IPS\Node\Model AND $skipPermission->_items !== NULL AND !\in_array( 'IPS\Content\FuturePublishing', class_implements( \get_called_class() ) ) AND \count( $where ) === 1 )
{
$countShortcut = TRUE;
$where[] = array( "({$col}=1 AND ( {$authorCol}={$member->member_id}{$extra} ) )" );
@@ -3086,7 +3156,7 @@ abstract class _Item extends \IPS\Content
/* Are we doing a pseudo-rand ordering? */
if( $order == '_rand' )
{
$subSelectClause .= static::_getRandomizationSql( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['date'] );
$subSelectClause .= static::_getRandomizationSql( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnId, static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['title'] );
}
$select = \IPS\Db::i()->select( $subSelectClause, static::$databaseTable, array_merge( $where, $containerWhere ), $order, $limit, ( $joinComments ? static::$databasePrefix . static::$databaseColumnId : NULL ), NULL, $queryFlags );
@@ -3163,7 +3233,7 @@ abstract class _Item extends \IPS\Content
/* Are we doing a pseudo-rand ordering? */
if( $order == '_rand' )
{
$selectClause .= static::_getRandomizationSql( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['date'] );
$selectClause .= static::_getRandomizationSql( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnId, static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['title'] );
}
if ( $joins !== NULL AND \count( $joins ) )
@@ -3237,12 +3307,13 @@ abstract class _Item extends \IPS\Content
/**
* Get randomization SQL query clause
*
* @param string $column Column to use for randomization
* @param string $id ID column to use
* @param string $title Text column to use
* @return string
*/
protected static function _getRandomizationSql( $column )
protected static function _getRandomizationSql( $id, $title )
{
return ', CONCAT( SUBSTR(' . $column . ', ' . '10, 10 ), SUBSTR(' . $column . ', ' . '9, 10 ), SUBSTR( ' . $column . ', ' . rand( 1, 9 ) . ', 10 ), SUBSTR( ' . $column . ', ' . rand( 4, 7 ) . ', 9 ) ) as _rand';
return ", SUBSTR( MD5( CONCAT( {$id}, {$title} ) ), " . rand( 2, 25 ) . " ) as _rand";
}
/**
@@ -3979,7 +4050,15 @@ abstract class _Item extends \IPS\Content
elseif ( $this->mapped('num_comments') >= 500 )
{
/* Its more efficient to just get the primary ids first without joins and then we can fetch all the data on the second query once the offset gets larger */
$ids = iterator_to_array( $class::db()->select( $class::$databasePrefix . $class::$databaseColumnId, $class::$databaseTable, $where, $order, array( $offset, $limit ) )->setKeyField( $class::$databasePrefix . $class::$databaseColumnId ) );
if( method_exists( $this, 'forceIndexForPaginatedIds' ) and $this->forceIndexForPaginatedIds() and $offset < 20000 )
{
/* Only use the index if we're not at a massive offset */
$ids = iterator_to_array( $class::db()->select( $class::$databasePrefix . $class::$databaseColumnId, $class::$databaseTable, $where, $order, array($offset, $limit) )->forceIndex( $this->forceIndexForPaginatedIds() )->setKeyField( $class::$databasePrefix . $class::$databaseColumnId ) );
}
else
{
$ids = iterator_to_array( $class::db()->select( $class::$databasePrefix . $class::$databaseColumnId, $class::$databaseTable, $where, $order, array($offset, $limit) )->setKeyField( $class::$databasePrefix . $class::$databaseColumnId ) );
}
$where = array( array( $class::db()->in( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId, array_keys( $ids ) ) ) );
$offset = 0;
@@ -4263,13 +4342,13 @@ abstract class _Item extends \IPS\Content
}
/* Solved count */
$commentClass = static::$commentClass;
if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Solvable' ) )
{
if ( $this->containerAllowsMemberSolvable() OR $this->containerAllowsSolvable() )
{
$memberIds = array();
$solvedCounts = array();
$commentClass = static::$commentClass;
$authorField = $commentClass::$databaseColumnMap['author'];
foreach( $results as $id => $data )
@@ -4295,6 +4374,18 @@ abstract class _Item extends \IPS\Content
}
}
/* Recognized content */
if ( \IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Recognizable' ) )
{
foreach( \IPS\Db::i()->select( '*', 'core_member_recognize', [ [ 'r_content_class=?', $commentClass ], [ \IPS\Db::i()->in('r_content_id', array_keys( $results ) ) ] ] ) as $row )
{
if ( isset( $results[ $row['r_content_id'] ] ) )
{
$results[ $row['r_content_id'] ]->recognized = \IPS\core\Achievements\Recognize::constructFromData( $row );
}
}
}
/* Return */
return ( $limit === 1 ) ? NULL : $results;
}
@@ -4357,6 +4448,21 @@ abstract class _Item extends \IPS\Content
*/
public static $checkPostsPerDay = TRUE;
/**
* Return the comment form object
*
* @return \IPS\Helpers\Form
*/
protected function _commentForm()
{
$idColumn = static::$databaseColumnId;
$form = new \IPS\Helpers\Form( 'commentform' . '_' . $this->$idColumn, static::$formLangPrefix . 'submit_comment' );
$form->class = 'ipsForm_vertical';
$form->hiddenValues['_contentReply'] = TRUE;
return $form;
}
/**
* Build comment form
*
@@ -4379,9 +4485,7 @@ abstract class _Item extends \IPS\Content
$commentIdColumn = $commentClass::$databaseColumnId;
$commentDateColumn = $commentClass::$databaseColumnMap['date'];
$form = new \IPS\Helpers\Form( 'commentform' . '_' . $this->$idColumn, static::$formLangPrefix . 'submit_comment' );
$form->class = 'ipsForm_vertical';
$form->hiddenValues['_contentReply'] = TRUE;
$form = $this->_commentForm();
$elements = $this->commentFormElements();
@@ -4457,15 +4561,21 @@ abstract class _Item extends \IPS\Content
/* Or post? */
$comment = $this->processCommentForm( $values );
unset( $this->commentPageCount );
$newPageCount = $this->commentPageCount();
if ( $comment->hidden() === -3 )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register' ) );
}
elseif( $comment->hidden() AND !\IPS\Member::loggedIn()->member_id )
{
\IPS\Output::i()->json( array( 'type' => 'add', 'id' => $comment->$commentIdColumn, 'page' => $newPageCount, 'total' => $this->mapped('num_comments'), 'content' => '', 'message' => \IPS\Member::loggedIn()->language()->addToStack( 'mod_queue_message' ) ) );
}
elseif ( \IPS\Request::i()->isAjax() )
{
$this->markRead( NULL, NULL, NULL, TRUE );
$newPageCount = $this->commentPageCount();
if ( $currentPageCount != $newPageCount )
{
\IPS\Output::i()->json( array( 'type' => 'redirect', 'page' => $newPageCount, 'total' => $this->mapped('num_comments'), 'content' => \IPS\Output::i()->replaceEmojiWithImages( $comment->html() ), 'url' => (string) $comment->url('find') ) );
@@ -4618,6 +4728,12 @@ abstract class _Item extends \IPS\Content
$return['hide'] = new \IPS\Helpers\Form\YesNo( 'hide', FALSE , FALSE, array( 'label' => 'hide' ) );
}
/* Post Anonymously */
if ( $container and $container->canPostAnonymously( $container::ANON_COMMENTS ) )
{
$return['post_anonymously'] = new \IPS\Helpers\Form\YesNo( 'post_anonymously', FALSE, FALSE, array( 'label' => \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_suffix' ) ), NULL, NULL, NULL, 'post_anonymously' );
}
return $return;
}
@@ -4649,7 +4765,13 @@ abstract class _Item extends \IPS\Content
$parameters = array_merge( array( 'reply-' . static::$application . '/' . static::$module . '-' . $this->$idColumn ), $comment->attachmentIds() );
\IPS\File::claimAttachments( ...$parameters );
/* Auto-follow */
/* Post anonymously */
if( isset( $values[ 'post_anonymously' ] ) )
{
$comment->setAnonymous( $values[ 'post_anonymously' ] );
}
/* Auto-follow - If posted anonymously we should set follow to anonymous as well */
if( isset( $values[ static::$formLangPrefix . 'auto_follow' ] ) )
{
if ( $values[ static::$formLangPrefix . 'auto_follow' ] and !\IPS\Member::loggedIn()->following( static::$application, $followArea, $this->$idColumn ) )
@@ -5133,12 +5255,6 @@ abstract class _Item extends \IPS\Content
*/
protected $_averageReviewRating = NULL;
/**
* @brief Have we output the average review rating?
* @note Structured data only allows this to be output once, so we need to track if we've output it
*/
public $printedAverageReviewRating = FALSE;
/**
* Get average review rating
*
@@ -5199,7 +5315,15 @@ abstract class _Item extends \IPS\Content
{
$this->_lastCommenter = \IPS\Member::load( $this->mapped('last_comment_by') );
if ( ! $this->_lastCommenter->member_id and isset( static::$databaseColumnMap['last_comment_name'] ) )
if ( ! $this->_lastCommenter->member_id and isset( static::$databaseColumnMap['is_anon'] ) )
{
$_lastComment = $this->comments( 1, 0, 'date', 'desc' );
if ( $_lastComment !== NULL AND $_lastComment->isAnonymous() )
{
$this->_lastCommenter->name = \IPS\Member::loggedIn()->language()->addToStack( "post_anonymously_placename" );
}
}
else if ( ! $this->_lastCommenter->member_id and isset( static::$databaseColumnMap['last_comment_name'] ) )
{
if ( $this->mapped('last_comment_name') )
{
@@ -5217,7 +5341,15 @@ abstract class _Item extends \IPS\Content
if( $_lastComment !== NULL )
{
$this->_lastCommenter = $this->comments( 1, 0, 'date', 'desc' )->author();
if ( $_lastComment->isAnonymous() )
{
$this->_lastCommenter = new \IPS\Member;
$this->_lastCommenter->name = \IPS\Member::loggedIn()->language()->addToStack( "post_anonymously_placename" );
}
else
{
$this->_lastCommenter = $this->comments( 1, 0, 'date', 'desc' )->author();
}
}
else
{
@@ -5416,7 +5548,7 @@ abstract class _Item extends \IPS\Content
return;
}
$columns = array( 'last_comment', 'last_comment_by', 'last_comment_name' );
$columns = array( 'last_comment', 'last_comment_by', 'last_comment_name', 'last_comment_anon' );
$resync = FALSE;
foreach ( $columns as $k )
{
@@ -5471,6 +5603,11 @@ abstract class _Item extends \IPS\Content
$lastCommentNameField = static::$databaseColumnMap['last_comment_name'];
$this->$lastCommentNameField = ( !$comment->author()->member_id and isset( $comment::$databaseColumnMap['author_name'] ) ) ? $comment->mapped('author_name') : $comment->author()->name;
}
if ( isset( static::$databaseColumnMap['last_comment_anon'] ) )
{
$lastCommentAnonField = static::$databaseColumnMap['last_comment_anon'];
$this->$lastCommentAnonField = $comment->isAnonymous();
}
}
catch ( \UnderflowException $e )
{
@@ -5846,15 +5983,45 @@ abstract class _Item extends \IPS\Content
/**
* Generate meta data between comments. It is assumed they all belong to the same topic
*
* @param array $comments Array of $comment objects
* @param array $comments Array of $comment objects
* @param bool $anonymous Anonymize the moderator actions
* @return array
*/
public function generateCommentMetaData( array $comments ) : array
public function generateCommentMetaData( array $comments, $anonymous = FALSE ) : array
{
if ( ! $this->showCommentMeta('time') and ! $this->showCommentMeta('moderation') )
{
return $comments;
}
$showAnonymous = FALSE;
if( $anonymous )
{
$containerClass = static::$containerNodeClass;
if( isset( $containerClass::$modPerm ) AND $containerClass::$modPerm )
{
$showAnonymous = !(
\IPS\Member::loggedIn()->modPermission( $containerClass::$modPerm ) === TRUE
or
\IPS\Member::loggedIn()->modPermission( $containerClass::$modPerm ) === -1
or
(
\is_array( \IPS\Member::loggedIn()->modPermission( $containerClass::$modPerm ) )
and
\in_array( $this->container()->_id, \IPS\Member::loggedIn()->modPermission( $containerClass::$modPerm ) )
)
);
}
else
{
$showAnonymous = TRUE;
}
}
$anonymous ? (bool) \IPS\Member::loggedIn()->modPermissions() : FALSE;
$lastDate = NULL;
@@ -5892,9 +6059,9 @@ abstract class _Item extends \IPS\Content
if ( ( $modActionDate > $commentDate and $modActionDate < $nextCommentDate ) or ( $modActionDate > $commentDate and !$next ) )
{
$commentDate = $modActionDate;
$blurb = NULL;
$langKey = 'comment_meta_moderation_' . $data['lang_key'];
$langKey = 'comment_meta_moderation_' . ( $showAnonymous ? 'anon_' : '' ) . $data['lang_key'];
/* Edits always get their own meta action bubble, so handle those individually */
if ( \in_array( $data['lang_key'], array( 'modlog__item_edit', 'modlog__comment_edit_title' ) ) )
@@ -5964,7 +6131,7 @@ abstract class _Item extends \IPS\Content
/* If this is just one action, generate a full bubble */
$comment->_data['metaData']['comment']['moderation'][$actions[0]['action']] = array(
'row' => $actions[0]['row'],
'blurb' => \IPS\Member::loggedIn()->language()->addToStack( $actions[0]['lang_key'], FALSE, array('htmlsprintf' => array($this->definiteArticle(), \IPS\Member::load( $data['member_id'] )->link())) ),
'blurb' => \IPS\Member::loggedIn()->language()->addToStack( $actions[0]['lang_key'], FALSE, array('htmlsprintf' => array($this->definiteArticle(), \IPS\Member::load( $actions[0]['row']['member_id'] )->link())) ),
'action' => $actions[0]['lang_key']
);
}
@@ -5979,7 +6146,7 @@ abstract class _Item extends \IPS\Content
$comment->_data['metaData']['comment']['moderation'][$groupIdx] = array(
'row' => $actions[0]['row'], // Use the first action in this group as the 'row', to provide the time etc. for all items in this bubble
'blurb' => \IPS\Member::loggedIn()->language()->addToStack( 'comment_meta_moderation_modlog__action_group', FALSE, array('htmlsprintf' => array($this->definiteArticle(), \IPS\Member::load( $data['member_id'] )->link(), \IPS\Member::loggedIn()->language()->formatList( $actionPhrases ) ) ) ),
'blurb' => \IPS\Member::loggedIn()->language()->addToStack( 'comment_meta_moderation_' . ( $showAnonymous ? 'anon_' : '' ) . 'modlog__action_group', FALSE, array('htmlsprintf' => array($this->definiteArticle(), \IPS\Member::load( $lastActionMember )->link(), \IPS\Member::loggedIn()->language()->formatList( $actionPhrases ) ) ) ),
'action' => $actions[0]['lang_key']
);
}
@@ -6034,6 +6201,11 @@ abstract class _Item extends \IPS\Content
*/
public function showCommentMeta( $key )
{
if( $key == 'moderation' AND \IPS\Member::loggedIn()->group['gbw_hide_inline_modevents'] )
{
return FALSE;
}
$metaKey = static::$showMetaSettingKey;
if ( isset( \IPS\Settings::i()->$metaKey ) and \IPS\Settings::i()->$metaKey )
@@ -6200,7 +6372,7 @@ abstract class _Item extends \IPS\Content
// OK
}
/* If it's pending approval, and we're the author it's fine */
elseif ( $this->hidden() === 1 and $this->author()->member_id == $member->member_id )
elseif ( $this->hidden() === 1 and $this->author()->member_id and $this->author()->member_id == $member->member_id )
{
// OK
}
@@ -6722,8 +6894,7 @@ abstract class _Item extends \IPS\Content
*/
public function onHide( $member )
{
$container = NULL;
if ( method_exists( $this, 'container' ) )
if ( method_exists( $this, 'container' ) AND !$this->skipContainerRebuild )
{
try
{
@@ -6759,13 +6930,11 @@ abstract class _Item extends \IPS\Content
*/
public function onUnhide( $approving, $member )
{
$container = NULL;
/* If approving, we may need to increase the post count */
if ( $approving AND isset( static::$commentClass ) )
{
$commentClass = static::$commentClass;
if ( ( static::$firstCommentRequired and $commentClass::incrementPostCount( $this->containerWrapper() ) ) or static::incrementPostCount( $this->containerWrapper() ) )
if ( !$this->isAnonymous() and ( static::$firstCommentRequired and $commentClass::incrementPostCount( $this->containerWrapper() ) ) or static::incrementPostCount( $this->containerWrapper() ) )
{
$this->author()->member_posts++;
$this->author()->save();
@@ -6773,7 +6942,7 @@ abstract class _Item extends \IPS\Content
}
/* Update container */
if ( method_exists( $this, 'container' ) )
if ( method_exists( $this, 'container' ) AND !$this->skipContainerRebuild )
{
try
{
@@ -6800,7 +6969,6 @@ abstract class _Item extends \IPS\Content
}
}
/**
* Warning Reference Key
*
@@ -6865,6 +7033,20 @@ abstract class _Item extends \IPS\Content
return $this->sharelinks;
}
/**
* Web Share Data
*
* @return array|NULL
*/
public function webShareData(): ?array
{
return array(
'title' => \IPS\Text\Parser::truncate( $this->mapped('title'), TRUE ),
'text' => \IPS\Text\Parser::truncate( $this->mapped('title'), TRUE ),
'url' => (string) $this->url()
);
}
/* !ReadMarkers */
/**
@@ -6974,7 +7156,7 @@ abstract class _Item extends \IPS\Content
$latestThing = 0;
foreach ( array( 'updated', 'last_comment', 'last_review' ) as $k )
{
if ( isset( static::$databaseColumnMap[ $k ] ) and ( $this->mapped( $k ) < time() AND $this->mapped( $k ) > $latestThing ) )
if ( isset( static::$databaseColumnMap[ $k ] ) and ( $this->mapped( $k ) <= time() AND $this->mapped( $k ) > $latestThing ) )
{
$latestThing = $this->mapped( $k );
}
@@ -7001,7 +7183,7 @@ abstract class _Item extends \IPS\Content
* @return int|NULL 0 = read. -1 = never read. 1 = updated since last read. NULL = unsupported
* @note When a node is marked read, we stop noting which individual content items have been read. Therefore, -1 vs 1 is not always accurate but rather goes on if the item was created
*/
public static function unreadFromData( \IPS\Member $member = NULL, $updateDate, $createDate, $itemId, $containerId, $limitToApp = TRUE )
public static function unreadFromData( ?\IPS\Member $member, $updateDate, $createDate, $itemId, $containerId, $limitToApp = TRUE )
{
/* Get the member */
$member = $member ?: \IPS\Member::loggedIn();
@@ -7034,7 +7216,7 @@ abstract class _Item extends \IPS\Content
}
else
{
$resetTime = ( $resetTimes > $member->marked_site_read ) ? $resetTimes : $member->marked_site_read;
$resetTime = ( !\is_array( $resetTimes ) AND $resetTimes > $member->marked_site_read ) ? $resetTimes : $member->marked_site_read;
}
/* If the reset time is after when this item was updated, it's read */
@@ -7251,8 +7433,14 @@ abstract class _Item extends \IPS\Content
{
$subWhere = array();
$checked = array();
foreach ( array( 'updated', 'last_comment', 'last_review' ) as $k )
foreach ( array( 'last_comment', 'last_review', 'updated' ) as $k )
{
/* If we already hit last_comment and/or last_review, skip updated since we don't mark as unread when stuff is updated */
if( \count( $checked ) AND $k == 'updated' )
{
break;
}
if ( isset( static::$databaseColumnMap[ $k ] ) )
{
if ( \is_array( static::$databaseColumnMap[ $k ] ) )
@@ -8091,6 +8279,9 @@ abstract class _Item extends \IPS\Content
/* Send notifications if necessary */
$this->sendNotifications();
/* Give out points */
$this->author()->achievementAction( 'core', 'NewContentItem', $this );
}
/**
@@ -8101,7 +8292,6 @@ abstract class _Item extends \IPS\Content
*/
public function onPublish( $member )
{
$container = NULL;
if ( method_exists( $this, 'container' ) )
{
try
@@ -8113,20 +8303,23 @@ abstract class _Item extends \IPS\Content
$container->_futureItems = ( $container->_futureItems > 0 ) ? $container->_futureItems - 1 : 0;
}
$container->_items = $container->_items + 1;
if ( isset( static::$commentClass ) )
if( !$this->skipContainerRebuild )
{
$container->_comments = $container->_comments + $this->mapped('num_comments');
$container->setLastComment();
}
if ( isset( static::$reviewClass ) )
{
$container->_reviews = $container->_reviews + $this->mapped('num_reviews');
$container->setLastReview();
}
$container->_items = $container->_items + 1;
$container->save();
if ( isset( static::$commentClass ) )
{
$container->_comments = $container->_comments + $this->mapped('num_comments');
$container->setLastComment();
}
if ( isset( static::$reviewClass ) )
{
$container->_reviews = $container->_reviews + $this->mapped('num_reviews');
$container->setLastReview();
}
$container->save();
}
}
catch ( \BadMethodCallException $e ) { }
}
@@ -8318,11 +8511,12 @@ abstract class _Item extends \IPS\Content
if ( $values = $form->values() )
{
\IPS\Db::i()->insert( 'core_ratings', array(
'class' => \get_called_class(),
'item_id' => $this->$idColumn,
'member' => (int) \IPS\Member::loggedIn()->member_id,
'rating' => $values['rating'],
'ip' => \IPS\Request::i()->ipAddress()
'class' => \get_called_class(),
'item_id' => $this->$idColumn,
'member' => (int) \IPS\Member::loggedIn()->member_id,
'rating' => $values['rating'],
'ip' => \IPS\Request::i()->ipAddress(),
'rating_date' => time()
), TRUE );
if ( isset( static::$databaseColumnMap['rating_average'] ) )
@@ -8560,7 +8754,9 @@ abstract class _Item extends \IPS\Content
'meta_class' => \get_class( $this ),
'meta_item_id' => $this->$idColumn,
'meta_type' => $type,
'meta_data' => $data
'meta_data' => $data,
'meta_item_author' => $this->author()->member_id,
'meta_added' => time()
) );
$column = static::$databaseColumnMap['meta_data'];
@@ -8644,6 +8840,49 @@ abstract class _Item extends \IPS\Content
\IPS\Db::i()->delete( 'core_content_meta', array( "meta_class=? AND meta_item_id=?", \get_class( $this ), $this->$idColumn ) );
}
/* ! Redirect links */
/**
* Store a redirect
*
* Saves a redirect so when this class:item_id is attempted to be loaded in the future, it 301 redirects to the new item
*
* @param \IPS\Content\Item $item The item to redirect to
*/
public function setRedirectTo( \IPS\Content\Item $item )
{
$idColumn = static::$databaseColumnId;
\IPS\Db::i()->insert( 'core_item_redirect', array(
'redirect_class' => \get_class( $item ),
'redirect_item_id' => $this->$idColumn,
'redirect_new_item_id' => $item->$idColumn
) );
}
/**
* Get the redirect to data
*
* Fetches the \IPS\Content\Item we want to redirect to
*
* @param object $class Class to look up
* @param int $id The ID to look up
* @param bool $checkPerms Check permissions when loading
* @throws \OutOfRangeException
*/
public static function getRedirectFrom( $id, $checkPerms=TRUE )
{
$idColumn = static::$databaseColumnId;
try
{
$method = ( $checkPerms ) ? 'loadAndCheckPerms' : 'load';
return static::$method( \IPS\Db::i()->select( 'redirect_new_item_id', 'core_item_redirect', array( 'redirect_class=? and redirect_item_id=?', \get_called_class(), $id ) )->first() );
}
catch( \UnderflowException $e )
{
throw new \OutOfRangeException;
}
}
/**
* Can perform an action on a message
*
@@ -8747,6 +8986,12 @@ abstract class _Item extends \IPS\Content
public function featureComment( \IPS\Content\Comment $comment, $note = NULL, \IPS\Member $member = NULL )
{
\IPS\Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->featureComment( $this, $comment, $note, $member );
/* Give points */
$comment->author()->achievementAction( 'core', 'ContentPromotion', [
'content' => $comment,
'promotype' => 'recommend' //Yeah it says feature, but it's really recommend
] );
}
/**
@@ -8815,7 +9060,7 @@ abstract class _Item extends \IPS\Content
* @param \IPS\Member|NULL $member
* @return void
*/
public function toggleItemModeration( $action, ?\IPS\Member $member = NULL ): void
public function toggleItemModeration( $action, ?\IPS\Member $member = NULL )
{
if ( !\in_array( $action, array( 'enable', 'disable' ) ) )
{