Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

+418 -105
View File
@@ -402,7 +402,9 @@ 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() );
parent::delete();
$this->expireWidgetCaches();
@@ -444,19 +446,22 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
/**
* Check comment against profanity filters
* Check comment against profanity filters but do not act on it
*
* @param bool $first Is this the first comment?
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
* @param string|NULL $content The content to check - useful for if the content needs to be checked first, before it gets saved to the database.
* @param string|NULL|bool $title The title of the content to check, or NULL to check the current title, or FALSE to not check at all.
* @return bool Whether to send unapproved notifications
* @param bool $first Is this the first comment?
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
* @param string|NULL $content The content to check - useful for if the content needs to be checked first, before it gets saved to the database.
* @param string|NULL|bool $title The title of the content to check, or NULL to check the current title, or FALSE to not check at all.
* @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
* @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 )
{
public function shouldTriggerProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL, $title=NULL, $autoSaveLocation=NULL, $autoSaveKeys=NULL, $imageUploads=[], &$hiddenByFilter = FALSE, &$itemHiddenByFilter = FALSE )
{
/* Set our content */
$content = $content ?: $this->content();
/* We need our item */
$item = $this;
if ( $this instanceof \IPS\Content\Comment )
@@ -482,31 +487,78 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
return FALSE;
}
/* And the author */
/* Check the author cannot bypass */
$member = $this->author();
/* Pass this through our profanity filters to see if it needs to be mod queued */
$sendNotifications = FALSE;
/* Do we need to recount item comments and container comments? */
$recount = FALSE;
try
if ( $member->group['g_bypass_badwords'] )
{
$hiddenByFilter = FALSE;
/* If this is a content item that doesn't require a first comment, or a comment that isn't the first comment, this just pass the content as it stands. */
if ( !$this->hidden() AND ( ( $this instanceof \IPS\Content\Item ) OR ( $this instanceof \IPS\Content\Comment AND ( !$item::$firstCommentRequired OR ( $item::$firstCommentRequired AND !$first ) ) ) ) AND !$member->group['g_bypass_badwords'] )
return FALSE;
}
/* First, check the image scanner */
$idColumn = static::$databaseColumnId;
$itemIdColumn = $item::$databaseColumnId;
if ( \IPS\Settings::i()->ips_imagescanner_enable )
{
if ( $edit )
{
$autoSaveLocation = $autoSaveLocation ?: ( $item::$application . '_' . mb_ucfirst( $item::$module ) );
if ( !$autoSaveKeys )
{
if ( $this instanceof \IPS\Content\Comment and ( !( $this instanceof \IPS\Content\Review ) or !$first or !$item::$firstCommentRequired ) )
{
$autoSaveKeys = [ $this->attachmentIds() ];
}
else
{
$autoSaveKeys = [ [ $item->$itemIdColumn ] ];
}
}
$clauses = [];
$binds = [];
foreach ( $autoSaveKeys as $_autoSaveKeys )
{
$idLookups = [];
foreach ( range( 1, \count( $_autoSaveKeys ) ) as $i )
{
$idLookups[]= "id{$i}=?";
}
$clauses[] = '( ' . implode( ' AND ', $idLookups ) . ' )';
$binds = array_merge( $binds, $_autoSaveKeys );
}
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member );
if ( $title AND !$hiddenByFilter )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member );
}
$attachmentsMapWhere = array_merge( array( 'location_key=? AND ( ' . implode( ' OR ', $clauses ) . ' )', $autoSaveLocation ), $binds );
}
/* 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 ) )
else
{
if ( $autoSaveKeys === NULL )
{
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
{
$container = $item->containerWrapper();
$autoSaveKeys[] = 'newContentItem-' . $item::$application . '/' . $item::$module . '-' . ( $container ? $container->_id : 0 );
}
else
{
if ( $this instanceof \IPS\Content\Review )
{
$autoSaveKeys[] = 'review-' . $item::$application . '/' . $item::$module . '-' . $item->$itemIdColumn;
}
else
{
$autoSaveKeys[] = 'reply-' . $item::$application . '/' . $item::$module . '-' . $item->$itemIdColumn;
}
}
}
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member );
if ( $title AND !$itemHiddenByFilter )
@@ -514,54 +566,142 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member );
}
if ( isset( $item::$databaseColumnMap['approved'] ) and $itemHiddenByFilter )
$attachmentsMapWhere = \IPS\Db::i()->in( 'temp', array_map( 'md5', $autoSaveKeys ) );
}
foreach ( \IPS\Db::i()->select( 'attach_moderation_status', 'core_attachments', [ 'attach_id IN (?)', \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', $attachmentsMapWhere ) ] ) as $attachModerationStatus )
{
if ( $attachModerationStatus === 'pending' )
{
/* 'approved' is easy, clear and concise */
$column = $item::$databaseColumnMap['approved'];
$item->$column = 0;
$item->save();
$recount = TRUE;
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
{
$itemHiddenByFilter = TRUE;
}
else
{
$hiddenByFilter = TRUE;
}
break;
}
elseif ( isset( $item::$databaseColumnMap['hidden'] ) and $itemHiddenByFilter )
}
if ( !$hiddenByFilter and !$itemHiddenByFilter )
{
foreach ( $imageUploads as $file )
{
/* 'hidden' is backwards */
$column = $item::$databaseColumnMap['hidden'];
$item->$column = 1;
$item->save();
$recount = TRUE;
if ( $file->requiresModeration )
{
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
{
$itemHiddenByFilter = TRUE;
}
else
{
$hiddenByFilter = TRUE;
}
break;
}
}
}
}
if( $edit === TRUE )
/* Then pass this through our profanity and link filters */
try
{
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
{
if ( !$itemHiddenByFilter )
{
$sendNotifications = TRUE;
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
}
if ( !$itemHiddenByFilter and $title )
{
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title );
}
}
else
{
if ( !$hiddenByFilter )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
}
if ( !$hiddenByFilter and $title )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title );
}
}
}
catch( \BadMethodCallException $e ) { }
if ( $hiddenByFilter === TRUE )
/* Return */
return ( $hiddenByFilter or $itemHiddenByFilter );
}
/**
* Check comment against profanity filters AND act on it
*
* @param bool $first Is this the first comment?
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
* @param string|NULL $content The content to check - useful for if the content needs to be checked first, before it gets saved to the database.
* @param string|NULL|bool $title The title of the content to check, or NULL to check the current title, or FALSE to not check at all.
* @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
* @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 it */
$hiddenByFilter = FALSE;
$itemHiddenByFilter = FALSE;
$sendNotifications = FALSE;
$item = ( $this instanceof \IPS\Content\Item ) ? $this : $this->item();
$this->shouldTriggerProfanityFilters( $first, $edit, $content, $title, $autoSaveLocation, $autoSaveKeys, $imageUploads, $hiddenByFilter, $itemHiddenByFilter );
/* If we need to hide the item, then do that */
if ( $itemHiddenByFilter )
{
$recount = TRUE;
$sendNotifications = $edit;
/* 'approved' is easy, clear and concise */
if ( isset( $item::$databaseColumnMap['approved'] ) )
{
$column = $item::$databaseColumnMap['approved'];
$item->$column = 0;
$item->save();
}
/* 'hidden' is backwards */
elseif ( isset( $item::$databaseColumnMap['hidden'] ) )
{
$column = $item::$databaseColumnMap['hidden'];
$item->$column = 1;
$item->save();
}
}
/* If we need to hide this, then do that */
if ( $hiddenByFilter === TRUE or $itemHiddenByFilter === TRUE )
{
$sendNotifications = TRUE;
/* 'approved' is easy, clear and concise */
if ( isset( static::$databaseColumnMap['approved'] ) )
{
/* 'approved' is easy, clear and concise */
$column = static::$databaseColumnMap['approved'];
$this->$column = 0;
$this->save();
}
/* 'hidden' is backwards */
else if ( isset( static::$databaseColumnMap['hidden'] ) )
{
/* 'hidden' is backwards */
$column = static::$databaseColumnMap['hidden'];
$this->$column = 1;
$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();
}
/* We have to save here because the status change is relevant to resyncCommentCounts() and resyncLastComment(), and our comment object changes we just set can be overwritten when those methods are called. */
$this->save();
$sendNotifications = TRUE;
}
if ( $recount === TRUE )
/* If we did either, then recount number of comments */
if ( $itemHiddenByFilter or $hiddenByFilter )
{
$item->resyncCommentCounts();
$item->resyncLastComment();
@@ -574,6 +714,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
}
/* Return */
return $sendNotifications;
}
@@ -646,7 +787,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/**
* Return a list of groups that cannot see this item
*
* @return NULL|Array
* @return NULL|array
*/
public function cannotViewGroups()
{
@@ -1235,7 +1376,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$this->sendApprovedNotification();
}
/* Award points */
$itemClass = \in_array( 'IPS\Content\Comment', class_parents( \get_called_class() ) ) ? static::$itemClass : \get_called_class();
if ( $this instanceof \IPS\Content\Item )
@@ -1246,7 +1387,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$this->author()->achievementAction( 'core', 'Review', $this );
}
elseif ( $this instanceof \IPS\Content\Comment and ( !$itemClass::$firstCommentRequired or $this->isFirst() ) )
elseif ( $this instanceof \IPS\Content\Comment and ( ( $itemClass::$firstCommentRequired AND ! $this->isFirst() ) OR ( ! $itemClass::$firstCommentRequired ) ) )
{
$this->author()->achievementAction( 'core', 'Comment', $this );
}
@@ -1455,19 +1596,23 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/**
* Report
*
* @param string $reportContent Report content message from member
* @param int $reportType Report type (see constants in \IPS\core\Reports\Report
* @param string $reportContent Report content message from member
* @param int $reportType Report type (see constants in \IPS\core\Reports\Report)
* @param \IPS\Member|NULL $member Member making the report (or NULL for loggedIn())
* @return \IPS\core\Reports\Report
* @throws \UnexpectedValueException If there is a permission error - you should only call this method after checking canReport
*/
public function report( $reportContent, $reportType=1 )
public function report( $reportContent, $reportType=1, $member=NULL )
{
$member = ( $member ) ? $member : \IPS\Member::loggedIn();
/* Permission check */
if ( $this->canReport() !== TRUE )
$permCheck = $this->canReport( $member );
if ( $permCheck !== TRUE )
{
throw new \UnexpectedValueException;
throw new \UnexpectedValueException( $permCheck );
}
/* Find or create an index */
$idColumn = static::$databaseColumnId;
try
@@ -1481,7 +1626,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$index->class = \get_called_class();
$index->content_id = $this->$idColumn;
$index->perm_id = $this->permId();
$index->first_report_by = (int) \IPS\Member::loggedIn()->member_id;
$index->first_report_by = (int) $member->member_id;
$index->first_report_date = time();
$index->last_updated = time();
$index->author = (int) $this->author()->member_id;
@@ -1502,10 +1647,10 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$reportInsert = array(
'rid' => $index->id,
'report' => $reportContent,
'report_by' => (int) \IPS\Member::loggedIn()->member_id,
'report_by' => (int) $member->member_id,
'date_reported' => time(),
'ip_address' => \IPS\Request::i()->ipAddress(),
'report_type' => \IPS\Member::loggedIn()->member_id ? $reportType : 0
'report_type' => $member->member_id ? $reportType : 0
);
$insertID = \IPS\Db::i()->insert( 'core_rc_reports', $reportInsert );
@@ -1596,12 +1741,19 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
case 'author':
$this->$col = $newAuthor->member_id ? $newAuthor->member_id : 0;
break;
case 'author_name':
case 'edit_member_name':
/* Real name will contain the custom guest name if available or '' if not */
$this->$col = $newAuthor->member_id ? $newAuthor->name : $newAuthor->real_name;
break;
case 'edit_member_name':
/* Real name will contain the custom guest name if available or '' if not.
But we only want to update the user if oldName is the same as the current edit_member_name
So if "Bob" edited his own post, you want that to change if Bob becomes Bob2.
But if a moderator edited it, we don't want to change that name. */
if ( $oldAuthor->name == $this->$col )
{
$this->$col = $newAuthor->member_id ? $newAuthor->name : $newAuthor->real_name;
}
break;
case 'is_anon':
/* We are specifying an author so turn off is_anon column */
if ( $newAuthor->member_id )
@@ -1621,6 +1773,29 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
}
/**
* Get language articles for the given container
*
* @param string $itemClass The classname of this content
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
*/
public static function articlesFromIndexData( $itemClass, ?array $containerData ): array
{
/* Object URL */
$indefiniteArticle = static::_indefiniteArticle( $containerData );
$definiteArticle = static::_definiteArticle( $containerData );
$definiteArticleUc = static::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
if ( \in_array( 'IPS\Content\Comment', class_parents( \get_called_class() ) ) )
{
$indefiniteArticle = $itemClass::_indefiniteArticle( $containerData );
$definiteArticle = $itemClass::_definiteArticle( $containerData );
$definiteArticleUc = $itemClass::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
}
return array( 'indefinite' => $indefiniteArticle, 'definite' => $definiteArticle, 'definite_uc' => $definiteArticleUc );
}
/**
* Get HTML for search result display
*
@@ -1654,9 +1829,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$itemUrl = $itemClass::urlFromIndexData( $indexData, $itemData, 'getPrefComment' );
/* Object URL */
$indefiniteArticle = static::_indefiniteArticle( $containerData );
$definiteArticle = static::_definiteArticle( $containerData );
$definiteArticleUc = static::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
if ( \in_array( 'IPS\Content\Comment', class_parents( \get_called_class() ) ) )
{
if ( \in_array( 'IPS\Content\Review', class_parents( \get_called_class() ) ) )
@@ -1669,17 +1841,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$objectUrl = $itemUrl->setQueryString( array( 'do' => 'findComment', 'comment' => $indexData['index_object_id'] ) );
$showRepUrl = $itemUrl->setQueryString( array( 'do' => 'showReactionsComment', 'comment' => $indexData['index_object_id'] ) );
}
$indefiniteArticle = $itemClass::_indefiniteArticle( $containerData );
$definiteArticle = $itemClass::_definiteArticle( $containerData );
$definiteArticleUc = $itemClass::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
}
else
{
$objectUrl = $itemUrl;
$showRepUrl = $itemUrl->setQueryString( 'do', 'showReactions' );
}
$articles = array( 'indefinite' => $indefiniteArticle, 'definite' => $definiteArticle, 'definite_uc' => $definiteArticleUc );
/* Articles language */
$articles = static::articlesFromIndexData( $itemClass, $containerData );
/* Container details */
$containerUrl = NULL;
@@ -1740,7 +1910,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
*/
public static function searchResultSnippet( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $view )
{
return $view == 'expanded' ? \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' )->searchResultSnippet( $indexData ) : '';
return $view == 'expanded' ? \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' )->searchResultSnippet( $indexData, $itemData ) : '';
}
/**
@@ -1752,17 +1922,32 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
* @param array $articles Articles language strings
* @param array $indexData Search index data
* @param array $itemData Data about the item
* @param bool $includeLinks Include links to member profile
* @return string
*/
public static function searchResultSummaryLanguage( $authorData, $articles, $indexData, $itemData )
public static function searchResultSummaryLanguage( $authorData, $articles, $indexData, $itemData, $includeLinks = TRUE )
{
$authorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $authorData['member_id'], $authorData['name'], $authorData['members_seo_name'], $authorData['member_group_id'] ?? \IPS\Settings::i()->guest_group, NULL, $indexData['index_is_anon'] );
if( $includeLinks )
{
$authorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $authorData['member_id'], $authorData['name'], $authorData['members_seo_name'], $authorData['member_group_id'] ?? \IPS\Settings::i()->guest_group, NULL, $indexData['index_is_anon'] );
}
else
{
$authorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userNameFromData( $authorData['name'], $authorData['member_group_id'] ?? \IPS\Settings::i()->guest_group, NULL, $indexData['index_is_anon'] );
}
if( \in_array( 'IPS\Content\Comment', class_parents( $indexData['index_class'] ) ) )
{
if( isset( $itemData['author'] ) )
{
$itemAuthorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $itemData['author']['member_id'], $itemData['author']['name'], $itemData['author']['members_seo_name'], $itemData['author']['member_group_id'] ?? \IPS\Settings::i()->guest_group );
if( $includeLinks )
{
$itemAuthorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $itemData['author']['member_id'], $itemData['author']['name'], $itemData['author']['members_seo_name'], $itemData['author']['member_group_id'] ?? \IPS\Settings::i()->guest_group );
}
else
{
$itemAuthorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userNameFromData( $itemData['author']['name'], $itemData['author']['member_group_id'] ?? \IPS\Settings::i()->guest_group );
}
}
if( \in_array( 'IPS\Content\Review', class_parents( $indexData['index_class'] ) ) )
@@ -1930,6 +2115,75 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
return \IPS\Email::template( 'core', '_genericContent', $type, array( $this, $includeLinks, $includeAuthor, $email ) );
}
/**
* Returns "Foo posted {{indefart}} in {{container}}, {{date}}
*
* @param \IPS\Lang|NULL $plaintextLanguage If specified, will return plaintext (not linking the user or the container in the language specified). If NULL, returns with links based on logged in user's theme and language
* @note This function was extracted from IPS\Promote
* @return string
*/
public function objectMetaDescription( $plaintextLanguage=NULL )
{
$object = $this;
$author = $this->author();
if ( $object instanceof \IPS\Content\Item )
{
$container = $object->containerWrapper();
if ( $container )
{
if ( !$plaintextLanguage )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_container', FALSE, array(
'htmlsprintf' => array( $author->link(), \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ),
'sprintf' => array( $object->indefiniteArticle(), $container->url(), $container->_title ),
) );
}
else
{
return $plaintextLanguage->addToStack( 'promote_metadescription_container_nolink', FALSE, array(
'sprintf' => array( $object->indefiniteArticle( $plaintextLanguage ), $container->getTitleForLanguage( $plaintextLanguage ), $author->name, \IPS\DateTime::ts( $this->mapped('date') ) ),
) );
}
}
else
{
if ( !$plaintextLanguage )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
'htmlsprintf' => array( $author->link(), \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ),
'sprintf' => array( $object->indefiniteArticle() )
) );
}
else
{
return $plaintextLanguage->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
'sprintf' => array( $object->indefiniteArticle( $plaintextLanguage ), $author->name, \IPS\DateTime::ts( $this->mapped('date') ) )
) );
}
}
}
else if ( $object instanceof \IPS\Content\Comment )
{
if ( !$plaintextLanguage )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
'htmlsprintf' => array( $author->link(), \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ),
'sprintf' => array( $object->indefiniteArticle() )
) );
}
else
{
return $plaintextLanguage->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
'sprintf' => array( $object->indefiniteArticle( $plaintextLanguage ), $author->name, \IPS\DateTime::ts( $this->mapped('date') ) )
) );
}
}
throw new \OutofRangeException('object_not_valid');
}
/**
* Get a count of the database table
@@ -2046,8 +2300,17 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
$followIds = array();
$followers = iterator_to_array( $this->notificationRecipients( array( $offset, static::NOTIFICATIONS_PER_BATCH ), $extra ) );
$followers = $this->notificationRecipients( array( $offset, static::NOTIFICATIONS_PER_BATCH ), $extra );
/* If $followers is NULL (which can be the case if the follows are just people following the author), just return as there is nothing to do */
if ( $followers === NULL )
{
return NULL;
}
/* If we're still here, our iterator may not necessarily be one that implements Countable so we need to convert it to an array. */
$followers = iterator_to_array( $followers );
if( !\count( $followers ) )
{
return NULL;
@@ -2351,21 +2614,28 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$item = $this;
}
/* We have to send a limit even though we want all records because otherwise the Database store does not return all columns */
foreach( \IPS\Session\Store::i()->getOnlineUsers( 0, 'desc', array( 0, 5000 ), NULL, TRUE ) as $session )
/** Item::url() can throw a LogicException exception in specific cases like when a Pages Record has no valid page */
try
{
if( mb_strpos( $session['location_url'], (string) $item->url() ) === 0 )
/* We have to send a limit even though we want all records because otherwise the Database store does not return all columns */
foreach( \IPS\Session\Store::i()->getOnlineUsers( 0, 'desc', array( 0, 5000 ), NULL, TRUE ) as $session )
{
$sessionData = $session;
$sessionData['location_url'] = NULL;
$sessionData['location_lang'] = NULL;
$sessionData['location_data'] = json_encode( array() );
$sessionData['current_id'] = 0;
$sessionData['location_permissions'] = 0;
if( mb_strpos( $session['location_url'], (string) $item->url() ) === 0 )
{
$sessionData = $session;
$sessionData['location_url'] = NULL;
$sessionData['location_lang'] = NULL;
$sessionData['location_data'] = json_encode( array() );
$sessionData['current_id'] = 0;
$sessionData['location_permissions'] = 0;
\IPS\Session\Store::i()->updateSession( $sessionData );
\IPS\Session\Store::i()->updateSession( $sessionData );
}
}
}
catch( \LogicException $e ){}
}
/**
@@ -2709,58 +2979,90 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$filters = array();
$filters['author'] = $this->author()->member_id;
/* All our Zapier Triggers for content have a hidden setting, so we're using this one global for items and comments */
$filters['hidden'] = (bool) $this->hidden();
return $filters;
}
/**
* Set anonymous state
*
* @param bool $state The state, TRUE for anonymous FALSE for not
* @param \IPS\Member|NULL $member The member posting anonymously or NULL for logged in member
* @param bool $state The state, TRUE for anonymous FALSE for not
* @param \IPS\Member|NULL $member The member posting anonymously or NULL for logged in member
* @return void
*/
public function setAnonymous( $state = TRUE, $member = NULL )
public function setAnonymous( bool $state = TRUE, \IPS\Member $member = NULL)
{
if( !$this instanceof \IPS\Content\Anonymous or !$this->container()->canPostAnonymously() )
if( !$this instanceof \IPS\Content\Anonymous )
{
throw new \BadMethodCallException;
throw new \BadMethodCallException();
}
$member = $member ?: \IPS\Member::loggedIn();
if( $state == $this->isAnonymous() )
{
return;
}
$class = \get_class( $this );
$idColumn = static::$databaseColumnId;
$anonColumn = static::$databaseColumnMap['is_anon'];
if( !$state )
{
try
{
$originalAuthor = \IPS\DB::i()->select( 'anonymous_member_id', 'core_anonymous_posts', array( 'anonymous_object_class=? and anonymous_object_id=?', $class, $this->$idColumn ) )->first();
$member = \IPS\Member::load( $originalAuthor );
}
catch (\UnderflowException $e )
{
$member = NULL;
}
}
$member = $member ?: \IPS\Member::loggedIn();
if( $state and !$this->container()->canPostAnonymously( 0, $member ) )
{
throw new \BadMethodCallException();
}
if( $state )
{
{
/* Insert the anonymous map */
$save = array(
'anonymous_member_id' => $member->member_id,
'anonymous_member_id' => ( $this->author()->member_id ) ? $this->author()->member_id : $member->member_id,
'anonymous_object_class' => $class,
'anonymous_object_id' => $this->$idColumn
);
\IPS\Db::i()->replace( 'core_anonymous_posts', $save );
$member = new \IPS\Member;
$this->$anonColumn = 1;
}
else
{
\IPS\Db::i()->delete( 'core_anonymous_posts', array( 'anonymous_member_id=? and anonymous_object_class=? and anonymous_object_id=?', $member->member_id, $class, $this->$idColumn ) );
\IPS\Db::i()->delete( 'core_anonymous_posts', array( 'anonymous_object_class=? and anonymous_object_id=?', $class, $this->$idColumn ) );
$this->$anonColumn = 0;
}
/* @todo only run the rest of the code here if the $anonColumn state is different from original. Waste of processing otherwise */
$this->save();
$this->changeAuthor( $member, FALSE );
if ( \in_array( 'IPS\Content\Comment', class_parents( $this ) ) )
{
$this->item()->rebuildFirstAndLastCommentData();
}
$this->expireWidgetCaches();
}
/**
* Is this an anonymous entry?
*
@@ -2770,4 +3072,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
return $this->mapped('is_anon');
}
/**
* Returns the content images
*
* @return array|NULL
* @throws \BadMethodCallException
*/
public function imageLabelsForSearch()
{
return [];
}
}