Version 4.2.0

This commit is contained in:
Neo committed 2025-12-19 05:44:59 -08:00
1 parent 0dc2aee5ed
commit 96997ddd8e
2074 files changed
+125454 -55780

No files matched your search

+212 -45
View File
@@ -1,12 +1,11 @@
<?php
/**
* @brief Content Comment Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 8 Jul 2013
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Content;
@@ -168,37 +167,13 @@ abstract class _Comment extends \IPS\Content
}
}
}
/* Check if profanity filters should mod-queue this comment */
$obj->checkProfanityFilters( $first );
/* Save the comment */
$obj->save();
/* Pass this through our profanity filters to see if it needs to be mod queued */
try
{
$hiddenByFilter = FALSE;
if ( !$obj->hidden() AND ( ! $item::$firstCommentRequired or ( $item::$firstCommentRequired and !$obj->isFirst() ) ) AND !$member->group['g_bypass_badwords'] )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $comment );
}
}
catch( \BadMethodCallException $e ) { }
if ( $hiddenByFilter === TRUE )
{
if ( isset( static::$databaseColumnMap['approved'] ) )
{
/* 'approved' is easy, clear and concise */
$column = static::$databaseColumnMap['approved'];
$obj->$column = 0;
}
else if ( isset( static::$databaseColumnMap['hidden'] ) )
{
/* 'hidden' is backwards */
$column = static::$databaseColumnMap['hidden'];
$obj->$column = 1;
}
$obj->save();
}
/* Increment post count */
try
{
@@ -271,6 +246,81 @@ abstract class _Comment extends \IPS\Content
/* Return */
return $obj;
}
/**
* Check comment against profanity filters
*
* @note We do not save here, as saving is done in user-land code after
* @param bool $first Is this the first comment?
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
* @return bool Whether to send unapproved notifications
*/
protected function checkProfanityFilters( $first=FALSE, $edit=TRUE )
{
/* We need our item */
$item = $this->item();
/* And the author */
$member = $this->author();
/* Pass this through our profanity filters to see if it needs to be mod queued */
$sendNotifications = FALSE;
try
{
$hiddenByFilter = FALSE;
if ( !$this->hidden() AND ( ! $item::$firstCommentRequired or ( $item::$firstCommentRequired and !$first ) ) AND !$member->group['g_bypass_badwords'] )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $this->content() );
}
/* If the comment is hidden (probably requires moderator approval) and this is the first post in the item that requires a comment (i.e. a post in a topic), then we need to set the item as requiring approval instead. */
elseif ( $item instanceof \IPS\Content\Hideable AND !$member->group['g_bypass_badwords'] AND ( $item::$firstCommentRequired and $first ) )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $this->content() );
if ( isset( $item::$databaseColumnMap['approved'] ) and $itemHiddenByFilter )
{
/* 'approved' is easy, clear and concise */
$column = $item::$databaseColumnMap['approved'];
$item->$column = 0;
$item->save();
}
elseif ( isset( $item::$databaseColumnMap['hidden'] ) and $itemHiddenByFilter )
{
/* 'hidden' is backwards */
$column = $item::$databaseColumnMap['hidden'];
$item->$column = 1;
$item->save();
}
if( $edit === TRUE )
{
$sendNotifications = TRUE;
}
}
}
catch( \BadMethodCallException $e ) { }
if ( $hiddenByFilter === TRUE )
{
if ( isset( static::$databaseColumnMap['approved'] ) )
{
/* 'approved' is easy, clear and concise */
$column = static::$databaseColumnMap['approved'];
$this->$column = 0;
}
else if ( isset( static::$databaseColumnMap['hidden'] ) )
{
/* 'hidden' is backwards */
$column = static::$databaseColumnMap['hidden'];
$this->$column = 1;
}
$sendNotifications = TRUE;
}
return $sendNotifications;
}
/**
* Join profile fields when loading comments?
@@ -378,6 +428,9 @@ abstract class _Comment extends \IPS\Content
$item->container()->save();
}
}
/* Are we tracking keywords? */
$this->checkKeywords( $this->content() );
}
/**
@@ -482,7 +535,7 @@ abstract class _Comment extends \IPS\Content
}
/* Can the member edit their own content? */
if ( $member->member_id == $this->author()->member_id and $member->group['g_edit_posts'] and ( !( $item instanceof \IPS\Content\Lockable ) or !$item->locked() ) )
if ( $member->member_id == $this->author()->member_id and ( $member->group['g_edit_posts'] == '1' or in_array( get_class( $item ), explode( ',', $member->group['g_edit_posts'] ) ) ) and ( !( $item instanceof \IPS\Content\Lockable ) or !$item->locked() ) )
{
if ( !$member->group['g_edit_cutoff'] )
{
@@ -518,7 +571,7 @@ abstract class _Comment extends \IPS\Content
}
catch ( \BadMethodCallException $e ) { }
return ( !$this->isFirst() and ( static::modPermission( 'hide', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and $member->group['gbw_soft_delete_own'] ) ) );
return ( !$this->isFirst() and ( static::modPermission( 'hide', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_hide_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_hide_own_posts'] ) ) ) ) ) );
}
/**
@@ -565,7 +618,7 @@ abstract class _Comment extends \IPS\Content
}
catch ( \BadMethodCallException $e ) { }
return ( !$this->isFirst() and ( static::modPermission( 'delete', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and $member->group['g_delete_own_posts'] ) ) );
return ( !$this->isFirst() and ( static::modPermission( 'delete', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_delete_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_delete_own_posts'] ) ) ) ) ) );
}
/**
@@ -596,6 +649,28 @@ abstract class _Comment extends \IPS\Content
return FALSE;
}
/**
* Can report?
*
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
* @return bool
*/
public function canReport( $member=NULL )
{
if ( !( $this instanceof \IPS\Content\ReportCenter ) )
{
return FALSE;
}
if ( $this->hidden() === 1 )
{
return FALSE;
}
$member = $member ?: \IPS\Member::loggedIn();
return ( $member->group['g_can_report'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_can_report'] ) ) );
}
/**
* Search Index Permissions
*
@@ -624,6 +699,11 @@ abstract class _Comment extends \IPS\Content
*/
public function isIgnored( $member=NULL )
{
if ( !\IPS\Settings::i()->ignore_system_on )
{
return FALSE;
}
if ( $member === NULL )
{
$member = \IPS\Member::loggedIn();
@@ -661,16 +741,46 @@ abstract class _Comment extends \IPS\Content
$valueField = static::$databaseColumnMap['content'];
$oldValue = $this->$valueField;
$this->$valueField = $newContent;
$isHiddenNow = $this->hidden();
/* Check if profanity filters should mod-queue this comment */
$sendNotifications = $this->checkProfanityFilters( $this->isFirst(), TRUE );
$this->save();
/* Send any new mention/quote notifications */
$this->sendAfterEditNotifications( $oldValue );
/* Resync the item - necessary as profanity filters may have just hidden an otherwise approved post */
$item = $this->item();
if( $isHiddenNow != $this->hidden() )
{
$item->resyncCommentCounts();
$item->resyncLastComment();
$item->save();
}
if ( $container = $item->containerWrapper() )
{
$container->resetCommentCounts();
$container->save();
}
/* Reindex */
if ( $this instanceof \IPS\Content\Searchable )
{
\IPS\Content\Search\Index::i()->index( $this );
}
/* Send notifications */
if ( $sendNotifications AND !in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
if( $this->hidden() === 1 )
{
$this->sendUnapprovedNotification();
}
}
}
/**
@@ -852,10 +962,11 @@ abstract class _Comment extends \IPS\Content
/**
* Move Comment to another item
*
* @param \IPS\Content\Item $item The item to move this comment too.
* @param \IPS\Content\Item $item The item to move this comment to
* @param bool $skip Skip rebuilding new/old content item data (used for multimod where we can do it in one go after)
* @return void
*/
public function move( \IPS\Content\Item $item )
public function move( \IPS\Content\Item $item, $skip=FALSE )
{
$oldItem = $this->item();
@@ -872,13 +983,16 @@ abstract class _Comment extends \IPS\Content
\IPS\Db::i()->update( 'core_notifications', array( 'item_id' => $item->$idColumn ), array( 'item_class=? and item_id=? and item_sub_class=? and item_sub_id=?', (string) get_class( $item ), $oldItem->$idColumn, (string) get_called_class(), $this->$commentIdColumn ) );
/* Update reputation */
if ( $this instanceof \IPS\Content\Reputation )
if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Reactable' ) )
{
\IPS\Db::i()->update( 'core_reputation_index', array( 'item_id' => $item->$idColumn ), array( 'class_type_id_hash=?', md5( get_class( $this ) . ':' . $oldItem->$idColumn ) ) );
}
$oldItem->rebuildFirstAndLastCommentData();
$item->rebuildFirstAndLastCommentData();
if( $skip === FALSE )
{
$oldItem->rebuildFirstAndLastCommentData();
$item->rebuildFirstAndLastCommentData();
}
/* Add to search index */
if ( $this instanceof \IPS\Content\Searchable )
@@ -937,13 +1051,19 @@ abstract class _Comment extends \IPS\Content
$item = NULL;
$itemId = $this->mapped('item');
}
/* Remove featured comment associations */
if( $this->isFeatured() AND $item )
{
\IPS\Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->unfeatureComment( $item, $this );
}
/* Unclaim attachments */
\IPS\File::unclaimAttachments( $itemClass::$application . '_' . ucfirst( $itemClass::$module ), $itemId, $this->$idColumn );
/* Reduce the number of comment/reviews count on the item but only if the item is unapproved or visible
* - hidden as opposed to unapproved items do not get included in either of the unapproved_comments/num_comments columns */
if( $this->hidden() !== -1 )
if( $this->hidden() !== -1 AND $this->hidden() !== -2 )
{
$columnName = ( $this->hidden() === 1 ) ? 'unapproved_comments' : 'num_comments';
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
@@ -1005,7 +1125,7 @@ abstract class _Comment extends \IPS\Content
{
\IPS\Member::load( $member )->recountNotifications();
}
/* Actually delete */
parent::delete();
@@ -1031,7 +1151,7 @@ abstract class _Comment extends \IPS\Content
{
if ( $item->container()->_comments !== NULL )
{
if ( !$this->hidden() )
if ( !$this->hidden() AND $this->hidden() !== -2 )
{
$item->container()->_comments = ( $item->container()->_comments > 0 ) ? ( $item->container()->_comments - 1 ) : 0;
}
@@ -1257,6 +1377,18 @@ abstract class _Comment extends \IPS\Content
}
}
/* No matter if we can or cannot view hidden items, we do not want these to show as they are queued for deletion */
if ( isset( static::$databaseColumnMap['hidden'] ) )
{
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['hidden'];
$where[] = array( "{$col}!=-2" );
}
else if ( isset( static::$databaseColumnMap['approved'] ) )
{
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['approved'];
$where[] = array( "{$col}!=-2" );
}
if ( $joinContainer AND isset( $itemClass::$containerNodeClass ) )
{
$containerClass = $itemClass::$containerNodeClass;
@@ -1368,6 +1500,30 @@ abstract class _Comment extends \IPS\Content
return array( $this->item()->$idColumn, $this->$commentIdColumn );
}
/**
* Returns the content images
*
* @param int|null $limit Number of attachments to fetch, or NULL for all
*
* @return array|NULL
* @throws \BadMethodCallException
*/
public function contentImages( $limit = NULL )
{
$idColumn = static::$databaseColumnId;
$item = $this->item();
$attachments = array();
$itemIdColumn = $item::$databaseColumnId;
$internal = \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', array( 'location_key=? and id1=? and id2=?', $item::$application . '_' . ucfirst( $item::$module ), $item->$itemIdColumn, $this->$idColumn ) );
foreach( \IPS\Db::i()->select( '*', 'core_attachments', array( array( 'attach_id IN(?)', $internal ), array( 'attach_is_image=1' ) ), 'attach_id ASC', $limit ) as $row )
{
$attachments[] = array( 'core_Attachment' => $row['attach_location'] );
}
return count( $attachments ) ? $attachments : NULL;
}
/**
* @brief Existing warning
*/
@@ -1414,6 +1570,17 @@ abstract class _Comment extends \IPS\Content
return NULL;
}
/**
* Is a featured comment?
*
* @return bool
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function isFeatured()
{
return \IPS\Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->isFeatured( $this );
}
/**
* Get output for API
*