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

+234 -32
View File
@@ -404,6 +404,12 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
\IPS\Db::i()->delete( 'core_content_featured', [ 'feature_content_id=? and feature_content_class=?', $this->$idColumn, \get_class( $this ) ] );
\IPS\Api\Webhook::fire( str_replace( '\\', '', \substr( \get_called_class(), 3 ) ) . '_delete', $this, $this->webhookFilters() );
try
{
\IPS\core\Approval::loadFromContent( \get_called_class(), $this->$idColumn )->delete();
}
catch( \OutOfRangeException $e ) { }
parent::delete();
@@ -455,9 +461,10 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
* @param string $autoSaveLocation The autosave location key of any editors to check, or NULL to use the default.
* @param string|NULL $autoSaveKeys The autosave keys (for new content) or attach lookup ids (for an edit) of any editors to check, or NULL to use the default.
* @param array $imageUploads Images that have been uploaded that may require moderation
* @param array|NULL $filtersMatched What matched, passed by reference
* @return bool Whether to send unapproved notifications (i.e. true if the content was hidden)
*/
public function shouldTriggerProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL, $title=NULL, $autoSaveLocation=NULL, $autoSaveKeys=NULL, $imageUploads=[], &$hiddenByFilter = FALSE, &$itemHiddenByFilter = FALSE )
public function shouldTriggerProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL, $title=NULL, $autoSaveLocation=NULL, $autoSaveKeys=NULL, $imageUploads=[], &$hiddenByFilter = FALSE, &$itemHiddenByFilter = FALSE, array &$filtersMatched = array() )
{
/* Set our content */
$content = $content ?: $this->content();
@@ -528,11 +535,23 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$binds = array_merge( $binds, $_autoSaveKeys );
}
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member );
if ( $title AND !$hiddenByFilter )
if ( $this instanceof \IPS\Content\Item OR ( $first AND $item::$firstCommentRequired ) )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member );
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member, $filtersMatched );
if ( $title AND !$itemHiddenByFilter )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member, $filtersMatched );
}
}
else
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member, $filtersMatched );
if ( $title AND !$hiddenByFilter )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member, $filtersMatched );
}
}
$attachmentsMapWhere = array_merge( array( 'location_key=? AND ( ' . implode( ' OR ', $clauses ) . ' )', $autoSaveLocation ), $binds );
@@ -559,11 +578,23 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
}
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member );
if ( $title AND !$itemHiddenByFilter )
if ( $this instanceof \IPS\Content\Item OR ( $first AND $item::$firstCommentRequired ) )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member );
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member, $filtersMatched );
if ( $title AND !$itemHiddenByFilter )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member, $filtersMatched );
}
}
else
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member, $filtersMatched );
if ( $title AND !$hiddenByFilter )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member, $filtersMatched );
}
}
$attachmentsMapWhere = \IPS\Db::i()->in( 'temp', array_map( 'md5', $autoSaveKeys ) );
@@ -602,6 +633,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
break;
}
}
if ( $hiddenByFilter OR $itemHiddenByFilter )
{
$log = new \IPS\core\Approval;
$log->content_class = \get_called_class();
$log->content_id = $this->$idColumn;
$log->held_reason = 'image';
$log->save();
}
}
}
@@ -612,22 +652,22 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
if ( !$itemHiddenByFilter )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member, $filtersMatched );
}
if ( !$itemHiddenByFilter and $title )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title );
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member, $filtersMatched );
}
}
else
{
if ( !$hiddenByFilter )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member, $filtersMatched );
}
if ( !$hiddenByFilter and $title )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title );
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member, $filtersMatched );
}
}
}
@@ -650,13 +690,20 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
* @return bool Whether to send unapproved notifications (i.e. true if the content was hidden)
*/
public function checkProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL, $title=NULL, $autoSaveLocation=NULL, $autoSaveKeys=NULL, $imageUploads=[] )
{
{
/* Check this content type is hideable */
if ( !( $this instanceof \IPS\Content\Hideable ) )
{
return FALSE;
}
/* Check it */
$hiddenByFilter = FALSE;
$itemHiddenByFilter = FALSE;
$sendNotifications = FALSE;
$filtersMatched = array();
$item = ( $this instanceof \IPS\Content\Item ) ? $this : $this->item();
$this->shouldTriggerProfanityFilters( $first, $edit, $content, $title, $autoSaveLocation, $autoSaveKeys, $imageUploads, $hiddenByFilter, $itemHiddenByFilter );
$this->shouldTriggerProfanityFilters( $first, $edit, $content, $title, $autoSaveLocation, $autoSaveKeys, $imageUploads, $hiddenByFilter, $itemHiddenByFilter, $filtersMatched );
/* If we need to hide the item, then do that */
if ( $itemHiddenByFilter )
@@ -698,6 +745,54 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$this->$column = ( $itemHiddenByFilter and $this instanceof \IPS\Content\Comment ) ? 2 : 1; # We use the special 2 flag to note it is only hidden because the parent is
$this->save();
}
$idColumn = static::$databaseColumnId;
$itemColumnId = $item::$databaseColumnId;
try
{
if ( $this instanceof \IPS\Content\Comment AND $item::$firstCommentRequired AND $this->isFirst() )
{
\IPS\core\Approval::loadFromContent( \get_class( $item ), $item->$itemColumnId );
}
else
{
\IPS\core\Approval::loadFromContent( \get_called_class(), $this->$idColumn );
}
}
catch( \OutOfRangeException $e )
{
if ( isset( $filtersMatched['type'] ) AND isset( $filtersMatched['match'] ) )
{
$log = new \IPS\core\Approval;
if ( $this instanceof \IPS\Content\Comment AND $item::$firstCommentRequired AND $this->isFirst() )
{
$log->content_class = \get_class( $item );
$log->content_id = $item->$itemColumnId;
}
else
{
$log->content_class = \get_called_class();
$log->content_id = $this->$idColumn;
}
$log->held_reason = $filtersMatched['type'];
switch( $filtersMatched['type'] )
{
case 'profanity':
$log->held_data = array( 'word' => $filtersMatched['match'] );
break;
case 'url':
$log->held_data = array( 'url' => $filtersMatched['match'] );
break;
case 'email':
$log->held_data = array( 'email' => $filtersMatched['match'] );
break;
}
$log->save();
}
}
}
/* If we did either, then recount number of comments */
@@ -706,8 +801,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$item->resyncCommentCounts();
$item->resyncLastComment();
$item->save();
if ( $container = $item->containerWrapper() )
if ( $first AND $container = $item->containerWrapper() )
{
$container->resetCommentCounts();
$container->save();
@@ -959,7 +1054,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$this->$column = $val;
$this->save();
if ( $action == 'feature' )
if ( $action == 'feature' and $this->author()->member_id )
{
\IPS\Db::i()->insert( 'core_content_featured', [
'feature_content_id' => $this->$idColumn,
@@ -1092,6 +1187,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
try
{
$idColumn = static::$databaseColumnId;
if ( $this->container() AND !$this->skipContainerRebuild )
{
$this->container()->resetCommentCounts();
@@ -1109,10 +1206,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
else
{
$idColumn = static::$databaseColumnId;
$this->container()->rebuildPostedIn( array($this->$idColumn) );
}
}
\IPS\core\Approval::loadFromContent( \get_called_class(), $this->$idColumn )->delete();
}
catch( \BadMethodCallException $e ) {}
catch( \OutOfRangeException $e ) {}
@@ -1256,9 +1354,9 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$this->save();
$this->onHide( $member );
$idColumn = static::$databaseColumnId;
if ( static::$hideLogKey )
{
$idColumn = static::$databaseColumnId;
\IPS\Db::i()->delete( 'core_soft_delete_log', array( 'sdl_obj_id=? AND sdl_obj_key=?', $this->$idColumn, static::$hideLogKey ) );
\IPS\Db::i()->insert( 'core_soft_delete_log', array(
'sdl_obj_id' => $this->$idColumn,
@@ -1283,6 +1381,12 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$this->expireWidgetCaches();
$this->adjustSessions();
try
{
\IPS\core\Approval::loadFromContent( \get_called_class(), $this->$idColumn )->delete();
}
catch( \OutOfRangeException $e ) { }
}
/**
@@ -1346,10 +1450,10 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
$this->save();
$this->onUnhide( ( $approving OR ( $pbr AND $this->hidden() === 0 ) ), $member );
$idColumn = static::$databaseColumnId;
if ( static::$hideLogKey )
{
$idColumn = static::$databaseColumnId;
\IPS\Db::i()->delete('core_soft_delete_log', array('sdl_obj_id=? AND sdl_obj_key=?', $this->$idColumn, static::$hideLogKey));
}
@@ -1405,6 +1509,12 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$this->expireWidgetCaches();
$this->adjustSessions();
try
{
\IPS\core\Approval::loadFromContent( \get_called_class(), $this->$idColumn )->delete();
}
catch( \OutOfRangeException $e ) { }
}
/**
@@ -1443,7 +1553,42 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
catch ( \UnderflowException $e )
{
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack('hidden');
/* If this is requiring approval and has a logged reason */
$hidden = $this->hidden();
$item = NULL;
if ( ( $this instanceof \IPS\Content\Comment ) )
{
$item = $this->item();
/* If this is the first comment, and it's required, then we need to check the items hidden status instead */
if ( $item::$firstCommentRequired AND $this->isFirst() AND $hidden !== 1 )
{
$hidden = $this->item()->hidden();
}
}
if ( $hidden === 1 )
{
/* If moderator, show the reason */
$reason = NULL;
if( $this->modPermission( 'unhide' ) )
{
$reason = ( $item and $item::$firstCommentRequired AND $this->isFirst() ) ? $item->approvalQueueReason() : $this->approvalQueueReason();
}
if ( $reason )
{
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack( 'hidden_with_reason', FALSE, array( 'sprintf' => array( $reason ) ) );
}
else
{
/* Otherwise show a message that the content requires approval before it can be edited. */
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack( 'hidden_awaiting_approval' );
}
}
else
{
$this->hiddenBlurb = \IPS\Member::loggedIn()->language()->addToStack('hidden');
}
}
}
@@ -1482,6 +1627,33 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
}
/**
* @brief Reason for requiring approval
*/
protected $_approvalQueueReason = NULL;
/**
* Approval Queue Reason
*
* @return bool|string
*/
public function approvalQueueReason()
{
if ( $this->_approvalQueueReason === NULL )
{
try
{
$idColumn = static::$databaseColumnId;
$this->_approvalQueueReason = \IPS\core\Approval::loadFromContent( \get_class( $this ), $this->$idColumn )->reason();
}
catch( \OutOfRangeException $e )
{
$this->_approvalQueueReason = NULL;
}
}
return $this->_approvalQueueReason;
}
/**
* Can promote this comment/item?
*
@@ -1615,6 +1787,9 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/* Find or create an index */
$idColumn = static::$databaseColumnId;
$item = ( $this instanceof \IPS\Content\Comment ) ? $this->item() : $this;
$itemIdColumn = $item::$databaseColumnId;
try
{
$index = \IPS\core\Reports\Report::load( $this->$idColumn, 'content_id', array( 'class=?', \get_called_class() ) );
@@ -1633,6 +1808,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$index->num_reports = 1;
$index->num_comments = 0;
$index->auto_moderation_exempt = 0;
$index->item_id = $item->$itemIdColumn;
$index->node_id = $item->containerWrapper() ? $item->containerWrapper()->_id : 0;
}
/* Only set this to a new report if it is not already under review */
@@ -1658,7 +1835,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/* Run automatic moderation */
$index->runAutomaticModeration();
/* Send notification to mods */
$moderators = array( 'm' => array(), 'g' => array() );
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $mod )
@@ -1671,21 +1848,38 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
if ( $canView === FALSE )
{
$perms = json_decode( $mod['perms'], TRUE );
if ( isset( $perms['can_view_reports'] ) AND $perms['can_view_reports'] === TRUE )
{
$canView = TRUE;
}
/* Got nodes? */
if ( $canView === TRUE and $container = $item->containerWrapper() and isset( $container::$modPerm ) )
{
if ( isset( $perms[ $container::$modPerm ] ) and $perms[ $container::$modPerm ] != '*' and $perms[ $container::$modPerm ] != -1 )
{
if ( empty( $perms[ $container::$modPerm ] ) or ! in_array( $item->mapped('container'), $perms[ $container::$modPerm ] ) )
{
$canView = FALSE;
}
}
}
}
if ( $canView === TRUE )
{
$moderators[ $mod['type'] ][] = $mod['id'];
}
}
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'report_center', $index, array( $index, $reportInsert, $this ) );
foreach ( \IPS\Db::i()->select( '*', 'core_members', ( \count( $moderators['m'] ) ? \IPS\Db::i()->in( 'member_id', $moderators['m'] ) . ' OR ' : '' ) . \IPS\Db::i()->in( 'member_group_id', $moderators['g'] ) . ' OR ' . \IPS\Db::i()->findInSet( 'mgroup_others', $moderators['g'] ) ) as $member )
foreach ( \IPS\Db::i()->select( '*', 'core_members', ( \count( $moderators['m'] ) ? \IPS\Db::i()->in( 'member_id', $moderators['m'] ) . ' OR ' : '' ) . \IPS\Db::i()->in( 'member_group_id', $moderators['g'] ) . ' OR ' . \IPS\Db::i()->findInSet( 'mgroup_others', $moderators['g'] ) ) as $mem )
{
$notification->recipients->attach( \IPS\Member::constructFromData( $member ) );
$memberObj = \IPS\Member::constructFromData( $mem );
if( $this->canView( $memberObj ) )
{
$notification->recipients->attach( $memberObj );
}
}
$notification->send();
@@ -1696,7 +1890,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/* Return */
return $index;
}
/**
* Change IP Address
* @param string $ip The new IP address
@@ -2433,10 +2627,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'unapproved_content', $this, array( $this, $this->author() ) );
foreach ( \IPS\Db::i()->select( '*', 'core_members', ( \count( $moderators['m'] ) ? \IPS\Db::i()->in( 'member_id', $moderators['m'] ) . ' OR ' : '' ) . \IPS\Db::i()->in( 'member_group_id', $moderators['g'] ) . ' OR ' . \IPS\Db::i()->findInSet( 'mgroup_others', $moderators['g'] ) ) as $member )
{
/* We don't need to notify the author of the content */
if( $this->author()->member_id != $member['member_id'] )
$member = \IPS\Member::constructFromData( $member );
/* We don't need to notify the author of the content or when member cannot view this content*/
if( $this->author()->member_id != $member->member_id AND $this->canView( $member ) )
{
$notification->recipients->attach(\IPS\Member::constructFromData($member));
$notification->recipients->attach( $member );
}
}
$notification->send();
@@ -2607,7 +2802,14 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
if( $this instanceof \IPS\Content\Comment )
{
$item = $this->item();
try
{
$item = $this->item();
}
catch( \OutOfRangeException $e )
{
return;
}
}
else
{