Version 4.7.23
This commit is contained in:
1 parent
7124a02564
commit
25ddeb65d6
1791 files changed
+76990
-44452
No files matched your search
@@ -11,6 +11,9 @@
|
||||
namespace IPS\Content\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use OutOfRangeException;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -201,9 +204,16 @@ class _ItemController extends \IPS\Api\Controller
|
||||
$class = $this->class;
|
||||
|
||||
$item = $class::load( $id );
|
||||
if ( $this->member and !$item->can( 'read', $this->member ) )
|
||||
if( $this->member )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
if ( method_exists($item, 'canView') and !$item->canView( $this->member ) )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
else if ( !$item->can( 'read', $this->member ) )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 200, $item->apiOutput( $this->member ) );
|
||||
@@ -536,9 +546,16 @@ class _ItemController extends \IPS\Api\Controller
|
||||
/* Init */
|
||||
$itemClass = $this->class;
|
||||
$item = $itemClass::load( $id );
|
||||
if ( $this->member and !$item->can( 'read', $this->member ) )
|
||||
if( $this->member )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
if ( method_exists($item, 'canView') and !$item->canView( $this->member ) )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
else if ( !$item->can( 'read', $this->member ) )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
}
|
||||
$itemIdColumn = $itemClass::$databaseColumnId;
|
||||
$where [] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=?', $item->$itemIdColumn );
|
||||
|
||||
@@ -134,13 +134,17 @@ trait ClubContainer
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $this instanceof \IPS\cms\Categories )
|
||||
{
|
||||
$values['club_node_public'] = \intval( \in_array( 'member', explode( ',', $this->club_view_permission ) ) or \mb_strpos( '*', $this->club_view_permission ) !== false );
|
||||
}
|
||||
if( isset( $values['club_node_public'] ) AND $values['club_node_public'] != $this->isPublic() )
|
||||
{
|
||||
$needToUpdatePermissions = TRUE;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
\IPS\Db::i()->update( 'core_clubs_node_map', array( 'name' => $values['club_node_name'], 'public' => $values['club_node_public'] ? $values['club_node_public'] : 0), array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $nodeClass, $this->_id ) );
|
||||
\IPS\Db::i()->update( 'core_clubs_node_map', array( 'name' => $values['club_node_name'], 'public' => isset( $values['club_node_public'] ) ? (int) $values['club_node_public'] : 0), array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $nodeClass, $this->_id ) );
|
||||
}
|
||||
|
||||
\IPS\Lang::saveCustom( $itemClass::$application, static::$titleLangPrefix . $this->_id, $values['club_node_name'] );
|
||||
@@ -322,7 +326,17 @@ trait ClubContainer
|
||||
|
||||
if( $member->canAccessModule( \IPS\Application\Module::get( 'core', 'clubs', 'front' ) ) )
|
||||
{
|
||||
static::$cachedClubNodes = static::nodesWithPermission( $permissionCheck, $member, $where );
|
||||
/* nodesWithPermission joins on the permissions table, which is not used for club categories */
|
||||
$nodes = [];
|
||||
foreach( static::nodesWithPermission( $permissionCheck, $member, $where ) as $node )
|
||||
{
|
||||
if( !( $node instanceof \IPS\cms\Categories ) or ( $permissionCheck AND $node->can( $permissionCheck ) ) )
|
||||
{
|
||||
$nodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
static::$cachedClubNodes = $nodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+85
-39
@@ -11,6 +11,9 @@
|
||||
namespace IPS\Content;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Http\Url;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -234,23 +237,6 @@ abstract class _Comment extends \IPS\Content
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/* Send notifications and dish out points */
|
||||
if ( !\in_array( 'IPS\Content\Review', class_parents( \get_called_class() ) ) )
|
||||
{
|
||||
if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) )
|
||||
{
|
||||
$obj->sendNotifications();
|
||||
if( !$item instanceof \IPS\core\Messenger\Conversation )
|
||||
{
|
||||
$member->achievementAction( 'core', 'Comment', $obj );
|
||||
}
|
||||
}
|
||||
else if( $obj->hidden() === 1 )
|
||||
{
|
||||
$obj->sendUnapprovedNotification();
|
||||
}
|
||||
}
|
||||
|
||||
/* Send webhook */
|
||||
if ( \in_array( $obj->hidden(), array( -1, 0, 1 ) ) ) // i.e. not post before register or pending deletion
|
||||
{
|
||||
@@ -273,11 +259,11 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
$event = 'comment_create';
|
||||
$properties = $obj->getDataLayerProperties();
|
||||
}
|
||||
|
||||
if ( $properties['content_area'] === 'personal_messages' )
|
||||
{
|
||||
return;
|
||||
if ( $properties['content_area'] === 'personal_messages' )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$properties = array_merge( $properties, array(
|
||||
@@ -291,6 +277,23 @@ abstract class _Comment extends \IPS\Content
|
||||
/* Update item */
|
||||
$obj->postCreate();
|
||||
|
||||
/* Send notifications and dish out points */
|
||||
if ( !\in_array( 'IPS\Content\Review', class_parents( \get_called_class() ) ) )
|
||||
{
|
||||
if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) )
|
||||
{
|
||||
$obj->sendNotifications();
|
||||
if( !$item instanceof \IPS\core\Messenger\Conversation )
|
||||
{
|
||||
$member->achievementAction( 'core', 'Comment', $obj );
|
||||
}
|
||||
}
|
||||
else if( $obj->hidden() === 1 )
|
||||
{
|
||||
$obj->sendUnapprovedNotification();
|
||||
}
|
||||
}
|
||||
|
||||
/* This needs to happen before the index is added */
|
||||
if( $item::$firstCommentRequired and $obj->isFirst() )
|
||||
{
|
||||
@@ -432,7 +435,7 @@ abstract class _Comment extends \IPS\Content
|
||||
if ( !$item->hidden() and ! $item->approvedButHidden() and $item->containerWrapper() and $item->container()->_comments !== NULL and !$item->isFutureDate() )
|
||||
{
|
||||
$item->container()->_comments = ( $item->container()->_comments + 1 );
|
||||
$item->container()->setLastComment( $this );
|
||||
$item->container()->setLastComment( $this, $item );
|
||||
$item->container()->save();
|
||||
}
|
||||
}
|
||||
@@ -442,7 +445,7 @@ abstract class _Comment extends \IPS\Content
|
||||
|
||||
if ( $item->containerWrapper() AND !$item->approvedButHidden() AND $this->hidden() == 1 AND $item->container()->_unapprovedComments !== NULL )
|
||||
{
|
||||
$item->container()->_unapprovedComments = $item->container()->_unapprovedComments + 1;
|
||||
$item->container()->_unapprovedComments = ( $item->container()->_unapprovedComments >= 0 ) ? ( $item->container()->_unapprovedComments + 1 ) : 0;
|
||||
$item->container()->save();
|
||||
}
|
||||
}
|
||||
@@ -463,7 +466,7 @@ abstract class _Comment extends \IPS\Content
|
||||
|
||||
/* Update trending */
|
||||
$class = \get_class( $item );
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Realtime::i()->isEnabled('trending') and $class::$includeInTrending )
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Application::trendingIsEnabled() and $class::$includeInTrending )
|
||||
{
|
||||
/* Score by timestamp for trending */
|
||||
try
|
||||
@@ -554,6 +557,49 @@ abstract class _Comment extends \IPS\Content
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array Shareable URLs
|
||||
*/
|
||||
protected static $shareableUrls = array();
|
||||
/**
|
||||
* Get a shareable URL
|
||||
*
|
||||
* @return Url
|
||||
*/
|
||||
public function shareableUrl( string $type='comment' )
|
||||
{
|
||||
$currentPage = \IPS\Dispatcher\Front::i()->currentPage ?? 1;
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$item = $this->item();
|
||||
$itemIdColumn = $item::$databaseColumnId;
|
||||
|
||||
/* Avoid building a single item's URL multiple times, such as when viewing a topic */
|
||||
if ( isset( static::$shareableUrls[ $type ][ $item->$itemIdColumn ] ) )
|
||||
{
|
||||
$url = static::$shareableUrls[ $type ][ $item->$itemIdColumn ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = $item->url()->setPage( 'page', $currentPage );
|
||||
|
||||
/* if we can do both comments and reviews, just return the findCommment url as we have tabs to navigate anyway */
|
||||
if ( isset( $item::$reviewClass ) and isset( $item::$commentClass ) )
|
||||
{
|
||||
if ( method_exists( $item, 'commentReviewTabs' ) )
|
||||
{
|
||||
if ( count( $item->commentReviewTabs() ) > 1 )
|
||||
{
|
||||
$url = $url->setQueryString( ['tab' => $type . 's'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static::$shareableUrls[ $type ][ $item->$itemIdColumn ] = $url;
|
||||
}
|
||||
|
||||
return $url->setFragment( 'find' . ucfirst( $type ) . '-' . $this->$idColumn );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get containing item
|
||||
*
|
||||
@@ -910,6 +956,7 @@ abstract class _Comment extends \IPS\Content
|
||||
if ( $item->container()->_comments !== NULL )
|
||||
{
|
||||
$item->container()->setLastComment();
|
||||
$item->container()->_comments = ( $item->container()->_comments > 0 ) ? ( $item->container()->_comments - 1 ) : 0;
|
||||
$item->container()->resetCommentCounts();
|
||||
$item->container()->save();
|
||||
}
|
||||
@@ -923,7 +970,7 @@ abstract class _Comment extends \IPS\Content
|
||||
|
||||
if ( \IPS\IPS::classUsesTrait( $item, 'IPS\Content\Statistics' ) )
|
||||
{
|
||||
$item->clearCachedStatistics();
|
||||
$item->clearCachedStatistics( TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,11 +1013,18 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
if ( $item->container()->_comments !== NULL )
|
||||
{
|
||||
$item->container()->setLastComment();
|
||||
$item->container()->setLastComment( $this, $this->item() );
|
||||
$item->container()->_comments = ( $item->container()->_comments + 1 );
|
||||
$item->container()->resetCommentCounts();
|
||||
$item->container()->save();
|
||||
}
|
||||
|
||||
if( $item->container()->_unapprovedComments !== NULL and $approving )
|
||||
{
|
||||
$item->container()->_unapprovedComments = ( $item->container()->_unapprovedComments > 0 ) ? ( $item->container()->_unapprovedComments - 1 ) : 0;
|
||||
$item->container()->save();
|
||||
}
|
||||
|
||||
/* Update mappings */
|
||||
if ( \IPS\IPS::classUsesTrait( $item->container(), 'IPS\Node\Statistics' ) )
|
||||
{
|
||||
@@ -1092,11 +1146,6 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this );
|
||||
}
|
||||
|
||||
if ( \IPS\IPS::classUsesTrait( $item, 'IPS\Content\Statistics' ) )
|
||||
{
|
||||
$item->clearCachedStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the content items URL to IndexNow */
|
||||
@@ -1115,9 +1164,6 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
\IPS\core\IndexNow::addUrlsToQueue( $noIndexUrls );
|
||||
}
|
||||
|
||||
/* Remove from Hive */
|
||||
\IPS\core\Hive::i()->removeContent( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1243,7 +1289,7 @@ abstract class _Comment extends \IPS\Content
|
||||
|
||||
if ( $item AND \IPS\IPS::classUsesTrait( $this->item(), 'IPS\Content\Statistics' ) )
|
||||
{
|
||||
$item->clearCachedStatistics();
|
||||
$item->clearCachedStatistics( TRUE );
|
||||
}
|
||||
|
||||
/* Delete any notifications telling people about this */
|
||||
@@ -1292,7 +1338,7 @@ abstract class _Comment extends \IPS\Content
|
||||
if ( !$this->hidden() AND $this->hidden() !== -2 )
|
||||
{
|
||||
$item->container()->_comments = ( $item->container()->_comments > 0 ) ? ( $item->container()->_comments - 1 ) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
$item->resyncLastComment();
|
||||
$item->save();
|
||||
@@ -1352,12 +1398,12 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
|
||||
/* Last comment */
|
||||
$this->item()->resyncLastComment();
|
||||
$this->item()->resyncLastReview();
|
||||
$this->item()->resyncLastComment( $this );
|
||||
$this->item()->resyncLastReview( $this );
|
||||
$this->item()->save();
|
||||
if ( $container = $this->item()->containerWrapper() )
|
||||
{
|
||||
$container->setLastComment();
|
||||
$container->setLastComment( updatedItem: $this->item() );
|
||||
$container->setLastReview();
|
||||
$container->save();
|
||||
}
|
||||
|
||||
+54
-24
@@ -11,6 +11,9 @@
|
||||
namespace IPS;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Content\Searchable;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -411,9 +414,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
catch( \OutOfRangeException $e ) { }
|
||||
|
||||
/* Remove from Hive */
|
||||
\IPS\core\Hive::i()->removeContent( $this );
|
||||
|
||||
parent::delete();
|
||||
|
||||
$this->expireWidgetCaches();
|
||||
@@ -879,7 +879,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public static function canSeeMultiModTools( \IPS\Member $member = NULL, \IPS\Node\Model $container = NULL )
|
||||
{
|
||||
return static::modPermission( 'pin', $member, $container ) or static::modPermission( 'unpin', $member, $container ) or static::modPermission( 'feature', $member, $container ) or static::modPermission( 'unfeature', $member, $container ) or static::modPermission( 'edit', $member, $container ) or static::modPermission( 'hide', $member, $container ) or static::modPermission( 'unhide', $member, $container ) or static::modPermission( 'delete', $member, $container );
|
||||
return static::modPermission( 'pin', $member, $container ) or static::modPermission( 'unpin', $member, $container ) or static::modPermission( 'feature', $member, $container ) or static::modPermission( 'unfeature', $member, $container ) or static::modPermission( 'edit', $member, $container ) or static::modPermission( 'hide', $member, $container ) or static::modPermission( 'unhide', $member, $container ) or static::modPermission( 'delete', $member, $container ) or static::modPermission( 'move', $member, $container );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1145,6 +1145,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$member = \IPS\Member::loggedIn();
|
||||
}
|
||||
|
||||
$originalState = $this->hidden();
|
||||
|
||||
/* Log it! */
|
||||
$log = new \IPS\core\DeletionLog;
|
||||
@@ -1181,6 +1183,23 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
/* Save*/
|
||||
$item->save();
|
||||
|
||||
/* Now the container */
|
||||
try
|
||||
{
|
||||
$containerColumn = ( $originalState === 1 ) ? '_unapprovedComments' : '_comments';
|
||||
if ( \in_array( 'IPS\Content\Review', class_parents( \get_called_class() ) ) )
|
||||
{
|
||||
$containerColumn = ( $originalState === 1 ) ? '_unapprovedReviews' : '_reviews';
|
||||
}
|
||||
|
||||
if( $item->container()->$containerColumn !== null )
|
||||
{
|
||||
$item->container()->$containerColumn = ( $item->container()->$containerColumn > 0 ) ? ( $item->container()->$containerColumn - 1 ) : 0;
|
||||
$item->container()->save();
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException ){}
|
||||
}
|
||||
|
||||
if ( $this instanceof \IPS\Content\Tags )
|
||||
@@ -1217,9 +1236,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
catch( \BadMethodCallException $e ) {}
|
||||
catch( \OutOfRangeException $e ) {}
|
||||
|
||||
/* Remove from Hive */
|
||||
\IPS\core\Hive::i()->removeContent( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1276,18 +1292,19 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
/* Save the changes */
|
||||
$this->save();
|
||||
|
||||
/* Reindex the now hidden content - if this is a content item with comments or reviews, then make sure to do those too. */
|
||||
if ( $this instanceof \IPS\Content\Item AND ( isset( static::$commentClass ) OR isset( static::$reviewClass ) ) )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired ) ? $this->firstComment() : $this );
|
||||
\IPS\Content\Search\Index::i()->indexSingleItem( $this );
|
||||
if( $this instanceof Searchable )
|
||||
{ /* Reindex the now hidden content - if this is a content item with comments or reviews, then make sure to do those too. */
|
||||
if( $this instanceof \IPS\Content\Item and ( isset( static::$commentClass ) or isset( static::$reviewClass ) ) )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired ) ? $this->firstComment() : $this );
|
||||
\IPS\Content\Search\Index::i()->indexSingleItem( $this );
|
||||
}else
|
||||
{
|
||||
/* Either this is a comment / review, or the item doesn't support comments or reviews, so we can just reindex it now. */
|
||||
\IPS\Content\Search\Index::i()->index( $this );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Either this is a comment / review, or the item doesn't support comments or reviews, so we can just reindex it now. */
|
||||
\IPS\Content\Search\Index::i()->index( $this );
|
||||
}
|
||||
|
||||
|
||||
/* Delete the log */
|
||||
if ( isset( $log ) )
|
||||
{
|
||||
@@ -1393,9 +1410,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
\IPS\core\Approval::loadFromContent( \get_called_class(), $this->$idColumn )->delete();
|
||||
}
|
||||
catch( \OutOfRangeException $e ) { }
|
||||
|
||||
/* Remove from Hive */
|
||||
\IPS\core\Hive::i()->removeContent( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1780,10 +1794,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
* @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())
|
||||
* @param array $guestDetails Details for a guest report
|
||||
* @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, $member=NULL )
|
||||
public function report( $reportContent, $reportType=1, $member=NULL, $guestDetails=array() )
|
||||
{
|
||||
$member = ( $member ) ? $member : \IPS\Member::loggedIn();
|
||||
|
||||
@@ -1836,8 +1851,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
'report_by' => (int) $member->member_id,
|
||||
'date_reported' => time(),
|
||||
'ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'report_type' => $member->member_id ? $reportType : 0
|
||||
'report_type' => ( ! $member->member_id and \IPS\Settings::i()->automoderation_enabled ) ? 0 : $reportType
|
||||
);
|
||||
|
||||
if ( count( $guestDetails ) and isset( $guestDetails['email'] ) )
|
||||
{
|
||||
/* Guest name can be optional */
|
||||
$reportInsert['guest_name'] = isset( $guestDetails['name'] ) ? $guestDetails['name'] : '';
|
||||
$reportInsert['guest_email'] = $guestDetails['email'];
|
||||
}
|
||||
|
||||
$insertID = \IPS\Db::i()->insert( 'core_rc_reports', $reportInsert );
|
||||
$reportInsert['id'] = $insertID;
|
||||
@@ -2632,6 +2654,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
)
|
||||
)
|
||||
and
|
||||
isset( $perms["{$modPermKey}{$title}"] )
|
||||
and
|
||||
$perms["{$modPermKey}{$title}"]
|
||||
)
|
||||
{
|
||||
@@ -3099,14 +3123,20 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
$extraData = json_encode( array( 'class' => \get_class( $this ), 'id' => $this->$idColumn ) );
|
||||
|
||||
$insert = [];
|
||||
foreach( $keywords as $keyword )
|
||||
{
|
||||
if( \in_array( $keyword, $words ) )
|
||||
{
|
||||
$date = $date ?: \IPS\DateTime::create()->getTimestamp();
|
||||
\IPS\Db::i()->insert( 'core_statistics', array( 'time' => $date, 'type' => 'keyword', 'value_4' => $keyword, 'extra_data' => $extraData ) );
|
||||
$insert[] = array( 'time' => $date, 'type' => 'keyword', 'value_4' => $keyword, 'extra_data' => $extraData );
|
||||
}
|
||||
}
|
||||
|
||||
if( \count( $insert ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_statistics', $insert );
|
||||
}
|
||||
}
|
||||
|
||||
/* !Search */
|
||||
|
||||
+118
-39
@@ -267,7 +267,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
catch( \Exception $e ) {}
|
||||
}
|
||||
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Realtime::i()->isEnabled('trending') and $class::$includeInTrending )
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Application::trendingIsEnabled() and $class::$includeInTrending )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -373,7 +373,12 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
catch ( \BadMethodCallException $e ) { }
|
||||
}
|
||||
|
||||
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) )
|
||||
{
|
||||
\IPS\Output::setCacheTime( \IPS\cloud\Application::getCacheDateForPage( $item->mapped('updated') ?: $item->mapped('date'), ( ( $item::$application === 'forums' and $item->isArchived() ) ? 'archived' : 'content' ) ) );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $item;
|
||||
}
|
||||
@@ -737,6 +742,19 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
{
|
||||
$item->setTags( $values[ $item::$formLangPrefix . 'tags' ] ?: array() );
|
||||
|
||||
/* Update the search index */
|
||||
if ( $item instanceof \IPS\Content\Searchable )
|
||||
{
|
||||
if( $item::$firstCommentRequired )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $item->firstComment() );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $item );
|
||||
}
|
||||
}
|
||||
|
||||
if ( \IPS\Request::i()->isAjax() )
|
||||
{
|
||||
/* Build html we'll need to display */
|
||||
@@ -804,7 +822,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$maxLength = \IPS\Settings::i()->max_title_length ?: 255;
|
||||
$titleField = $item::$databaseColumnMap['title'];
|
||||
|
||||
if( \strlen( \IPS\Request::i()->newTitle ) > $maxLength )
|
||||
if( \mb_strlen( \IPS\Request::i()->newTitle ) > $maxLength )
|
||||
{
|
||||
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack( 'form_maxlength', FALSE, array( 'pluralize' => array( $maxLength ) ) ) );
|
||||
}
|
||||
@@ -819,6 +837,11 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( \IPS\Request::i()->newTitle ) ) ) );
|
||||
}
|
||||
/* Stop doing anything if the title wasn't changed */
|
||||
if( $item->$titleField == $newTitle->value )
|
||||
{
|
||||
\IPS\Output::i()->json( $item->$titleField );
|
||||
}
|
||||
|
||||
$item->$titleField = $newTitle->value;
|
||||
$idField = $item::$databaseColumnId;
|
||||
@@ -844,15 +867,20 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
if ( $item instanceof \IPS\Content\Searchable )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $item );
|
||||
}
|
||||
|
||||
/* Only add the mod log entry if we actually changed the title */
|
||||
if( $item->$titleField !== $oldTitle )
|
||||
{
|
||||
\IPS\Session::i()->modLog( 'modlog__comment_edit_title', array( (string) $item->url() => FALSE, $item->$titleField => FALSE, $oldTitle => FALSE ), $item );
|
||||
/* If it's an item which requires a first comment, send the comment */
|
||||
if( $item::$firstCommentRequired )
|
||||
{
|
||||
$class = $item->firstComment();
|
||||
}
|
||||
else
|
||||
{
|
||||
$class = $item;
|
||||
}
|
||||
\IPS\Content\Search\Index::i()->index( $class );
|
||||
}
|
||||
|
||||
\IPS\Session::i()->modLog( 'modlog__comment_edit_title', array( (string) $item->url() => FALSE, $item->$titleField => FALSE, $oldTitle => FALSE ), $item );
|
||||
|
||||
\IPS\Output::i()->json( $item->$titleField );
|
||||
}
|
||||
catch( \LogicException $e )
|
||||
@@ -1378,17 +1406,27 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
return;
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
|
||||
$options = array( \IPS\core\Reports\Report::TYPE_MESSAGE => \IPS\Member::loggedIn()->language()->addToStack('report_message_item') );
|
||||
foreach( \IPS\core\Reports\Types::roots() as $type )
|
||||
{
|
||||
$options[ $type->id ] = $type->_title;
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'report_type', NULL, FALSE, array( 'options' => $options ) ) );
|
||||
}
|
||||
|
||||
$options = array( \IPS\core\Reports\Report::TYPE_MESSAGE => \IPS\Member::loggedIn()->language()->addToStack('report_message_item') );
|
||||
foreach( \IPS\core\Reports\Types::roots() as $type )
|
||||
{
|
||||
$options[ $type->id ] = $type->_title;
|
||||
}
|
||||
|
||||
if ( count( $options ) > 1 )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'report_type', NULL, FALSE, ['options' => $options] ) );
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'report_message', NULL, FALSE, array( 'app' => 'core', 'key' => 'Reports', 'autoSaveKey' => "report-{$class::$application}-{$class::$module}-{$item->$idColumn}", 'minimize' => \IPS\Request::i()->isAjax() ? 'report_message_placeholder' : NULL ) ) );
|
||||
if ( ! \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->report_capture_guest_details )
|
||||
{
|
||||
/* Guest details */
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'report_guest_name', null, \IPS\Settings::i()->report_guest_details_name_mandatory ) );
|
||||
$form->add( new \IPS\Helpers\Form\Email( 'report_guest_email', null, true ) );
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'report_message', NULL, (bool) \IPS\Settings::i()->report_content_mandatory, array( 'app' => 'core', 'key' => 'Reports', 'autoSaveKey' => "report-{$class::$application}-{$class::$module}-{$item->$idColumn}", 'minimize' => \IPS\Settings::i()->report_content_mandatory ? 'report_message_placeholder_mandatory' : 'report_message_placeholder' ) ) );
|
||||
if ( !\IPS\Request::i()->isAjax() )
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['report_message'] = \IPS\Member::loggedIn()->language()->addToStack('report_message_fallback');
|
||||
@@ -1400,7 +1438,26 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
$report = $item->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0 );
|
||||
$guestDetails = [];
|
||||
if ( ! \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->report_capture_guest_details )
|
||||
{
|
||||
$guestDetails = [ 'name' => $values['report_guest_name'], 'email' => $values['report_guest_email'] ];
|
||||
|
||||
/* Has this guest already reported this? */
|
||||
try
|
||||
{
|
||||
$index = \IPS\Db::i()->select( '*', 'core_rc_index', [ 'class=? and content_id=?', \get_class( $item ), $item->$idColumn ] )->first();
|
||||
$report = \IPS\Db::i()->select( '*', 'core_rc_reports', [ 'rid=? and guest_email=?', $index['id'], $values['report_guest_email'] ] )->first();
|
||||
|
||||
\IPS\Output::i()->error( 'report_err_already_reported', '2S136/11', 403, '' );
|
||||
}
|
||||
catch( \UnderflowException )
|
||||
{
|
||||
/* The query failed, so this is a new report */
|
||||
}
|
||||
}
|
||||
|
||||
$report = $item->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0, null, $guestDetails );
|
||||
\IPS\File::claimAttachments( "report-{$class::$application}-{$class::$module}-{$item->$idColumn}", $report->id );
|
||||
if ( \IPS\Request::i()->isAjax() )
|
||||
{
|
||||
@@ -3154,7 +3211,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
|
||||
/* Show form */
|
||||
$form = new \IPS\Helpers\Form( NULL, 'report_submit' );
|
||||
$form = new \IPS\Helpers\Form( 'report_submit', 'report_submit' );
|
||||
$form->class = 'ipsForm_vertical';
|
||||
$itemIdColumn = $class::$databaseColumnId;
|
||||
$idColumn = $comment::$databaseColumnId;
|
||||
@@ -3172,17 +3229,27 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
return;
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
|
||||
$options = array( \IPS\core\Reports\Report::TYPE_MESSAGE => \IPS\Member::loggedIn()->language()->addToStack('report_message_comment') );
|
||||
foreach( \IPS\core\Reports\Types::roots() as $type )
|
||||
{
|
||||
$options[ $type->id ] = $type->_title;
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'report_type', NULL, FALSE, array( 'options' => $options ) ) );
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'report_message', NULL, FALSE, array( 'app' => 'core', 'key' => 'Reports', 'autoSaveKey' => "report-{$class::$application}-{$class::$module}-{$item->$itemIdColumn}-{$comment->$idColumn}", 'minimize' => 'report_message_placeholder' ) ) );
|
||||
|
||||
$options = array( \IPS\core\Reports\Report::TYPE_MESSAGE => \IPS\Member::loggedIn()->language()->addToStack('report_message_comment') );
|
||||
foreach( \IPS\core\Reports\Types::roots() as $type )
|
||||
{
|
||||
$options[ $type->id ] = $type->_title;
|
||||
}
|
||||
|
||||
if ( count( $options ) > 1 )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'report_type', null, false, ['options' => $options] ) );
|
||||
}
|
||||
|
||||
if ( ! \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->report_capture_guest_details )
|
||||
{
|
||||
/* Guest details */
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'report_guest_name', null, \IPS\Settings::i()->report_guest_details_name_mandatory ) );
|
||||
$form->add( new \IPS\Helpers\Form\Email( 'report_guest_email', null, true ) );
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'report_message', null, (bool) \IPS\Settings::i()->report_content_mandatory, array( 'app' => 'core', 'key' => 'Reports', 'autoSaveKey' => "report-{$class::$application}-{$class::$module}-{$item->$itemIdColumn}-{$comment->$idColumn}", 'minimize' => \IPS\Settings::i()->report_content_mandatory ? 'report_message_placeholder_mandatory' : 'report_message_placeholder' ) ) );
|
||||
if ( !\IPS\Request::i()->isAjax() )
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['report_message'] = \IPS\Member::loggedIn()->language()->addToStack('report_message_fallback');
|
||||
@@ -3195,7 +3262,26 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
$report = $comment->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0 );
|
||||
$guestDetails = [];
|
||||
if ( ! \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->report_capture_guest_details )
|
||||
{
|
||||
$guestDetails = [ 'name' => $values['report_guest_name'], 'email' => $values['report_guest_email'] ];
|
||||
|
||||
/* Has this guest already reported this? */
|
||||
try
|
||||
{
|
||||
$index = \IPS\Db::i()->select( '*', 'core_rc_index', [ 'class=? and content_id=?', \get_class( $comment ), $comment->$idColumn ] )->first();
|
||||
$report = \IPS\Db::i()->select( '*', 'core_rc_reports', [ 'rid=? and guest_email=?', $index['id'], $values['report_guest_email'] ] )->first();
|
||||
|
||||
\IPS\Output::i()->error( 'report_err_already_reported', '2S136/11', 403, '' );
|
||||
}
|
||||
catch( \UnderflowException )
|
||||
{
|
||||
/* The query failed, so this is a new report */
|
||||
}
|
||||
}
|
||||
|
||||
$report = $comment->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0, null, $guestDetails );
|
||||
\IPS\File::claimAttachments( "report-{$class::$application}-{$class::$module}-{$item->$itemIdColumn}-{$comment->$idColumn}", $report->id );
|
||||
if ( \IPS\Request::i()->isAjax() )
|
||||
{
|
||||
@@ -3671,13 +3757,6 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$item->rebuildFirstAndLastCommentData();
|
||||
$oldItem->rebuildFirstAndLastCommentData();
|
||||
|
||||
/* Update popular data */
|
||||
if( method_exists( $item, 'rebuildPopularTime' ) )
|
||||
{
|
||||
$item->rebuildPopularTime();
|
||||
$oldItem->rebuildPopularTime();
|
||||
}
|
||||
|
||||
/* Option to do some post split stuff */
|
||||
if ( method_exists( $item, 'splitComplete' ) )
|
||||
{
|
||||
|
||||
+199
-153
@@ -11,6 +11,9 @@
|
||||
namespace IPS\Content;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Http\Url;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -482,7 +485,7 @@ abstract class _Item extends \IPS\Content
|
||||
'follow_area' => $followArea,
|
||||
'follow_rel_id' => $obj->$idColumn,
|
||||
'follow_member_id' => \IPS\Member::loggedIn()->member_id,
|
||||
'follow_is_anon' => 0,
|
||||
'follow_is_anon' => isset( $values[ 'post_anonymously' ] ) ? (bool) $values[ 'post_anonymously' ] : 0,
|
||||
'follow_added' => time() + 1, // Make sure streams show follows after content is created
|
||||
'follow_notify_do' => 1,
|
||||
'follow_notify_meta' => '',
|
||||
@@ -629,8 +632,12 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
if( isset( static::$databaseColumnMap['is_future_entry'] ) )
|
||||
{
|
||||
$isFutureEntry = ( $time->getTimestamp() > time() );
|
||||
$column = static::$databaseColumnMap['is_future_entry'];
|
||||
$this->$column = $time->getTimestamp() > time();
|
||||
if( $this->$column != $isFutureEntry )
|
||||
{
|
||||
$this->$column = $isFutureEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -769,7 +776,7 @@ abstract class _Item extends \IPS\Content
|
||||
$return['guest_name'] = new \IPS\Helpers\Form\Text( 'guest_name', NULL, FALSE, array( 'minLength' => \IPS\Settings::i()->min_user_name_length, 'maxLength' => \IPS\Settings::i()->max_user_name_length, 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('comment_guest_name'), 'htmlAutocomplete' => "username" ), NULL, NULL, NULL, 'guest_name' );
|
||||
}
|
||||
}
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' and \IPS\Settings::i()->guest_captcha )
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' )
|
||||
{
|
||||
$return['captcha'] = new \IPS\Helpers\Form\Captcha;
|
||||
}
|
||||
@@ -1343,8 +1350,8 @@ abstract class _Item extends \IPS\Content
|
||||
if ( $this instanceof \IPS\Content\FuturePublishing AND isset( $values[ static::$formLangPrefix . 'date' ] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['is_future_entry'];
|
||||
|
||||
if ( $container AND $this->$column )
|
||||
|
||||
if ( $container AND isset( $this->changed[ $column ] ) AND !$this->changed[ $column ] ) // If the changed value is now false, it has just been published
|
||||
{
|
||||
if ( ( ! ( $values[ static::$formLangPrefix . 'date' ] instanceof \IPS\DateTime ) AND $values[ static::$formLangPrefix . 'date' ] == 0 ) OR ( $values[ static::$formLangPrefix . 'date' ] instanceof \IPS\DateTime AND $values[ static::$formLangPrefix . 'date' ]->getTimestamp() <= time() ) )
|
||||
{
|
||||
@@ -1352,7 +1359,7 @@ abstract class _Item extends \IPS\Content
|
||||
$this->publish();
|
||||
}
|
||||
}
|
||||
else if ( $container and ! $this->$column )
|
||||
else if ( $container AND isset( $this->changed[ $column ] ) AND $this->changed[ $column ] === TRUE ) // If the changed value is true, it has just been unpublished
|
||||
{
|
||||
if ( $values[ static::$formLangPrefix . 'date' ] instanceof \IPS\DateTime AND $values[ static::$formLangPrefix . 'date' ]->getTimestamp() > time() )
|
||||
{
|
||||
@@ -1472,12 +1479,7 @@ abstract class _Item extends \IPS\Content
|
||||
*/
|
||||
protected function processAfterTagUpdate()
|
||||
{
|
||||
/* If we edited tags for a topic, we have to manually update search index because it wants
|
||||
the first comment and not the content item itself. */
|
||||
if ( $this instanceof \IPS\Content\Item and $this instanceof \IPS\Content\Searchable and static::$firstCommentRequired and $this->firstComment() )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this->firstComment() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1638,7 +1640,26 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a shareable URL
|
||||
*
|
||||
* @param int|null $commentId
|
||||
* @return Url
|
||||
*/
|
||||
public function shareableUrl( int $commentId = NULL ): Url
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$url = $this->url();
|
||||
|
||||
if ( $commentId )
|
||||
{
|
||||
$url = $url->setFragment( 'findComment-' . $commentId );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL from index data
|
||||
*
|
||||
@@ -1825,7 +1846,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( $loadedExtensions[ $map['location_key'] ]->attachmentPermissionCheck( \IPS\Member::loggedIn(), $map['id1'], $map['id2'], $map['id3'], $row, TRUE ) )
|
||||
if ( method_exists( $loadedExtensions[ $map['location_key'] ], 'attachmentPermissionCheck') and $loadedExtensions[ $map['location_key'] ]->attachmentPermissionCheck( \IPS\Member::loggedIn(), $map['id1'], $map['id2'], $map['id3'], $row, TRUE ) )
|
||||
{
|
||||
$attachments[] = array( 'core_Attachment' => $row['attach_location'] );
|
||||
}
|
||||
@@ -1979,11 +2000,11 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
if ( isset( static::$commentClass ) and $oldContainer->_unapprovedComments !== NULL and isset( static::$databaseColumnMap['unapproved_comments'] ) )
|
||||
{
|
||||
$oldContainer->_unapprovedComments = \intval( $oldContainer->_unapprovedComments - $this->mapped('unapproved_comments') );
|
||||
$oldContainer->_unapprovedComments = ( $oldContainer->_unapprovedComments > 0 ) ? \intval( $oldContainer->_unapprovedComments - $this->mapped('unapproved_comments') ) : 0;
|
||||
}
|
||||
if ( isset( static::$reviewClass ) and $oldContainer->_unapprovedReviews !== NULL and isset( static::$databaseColumnMap['unapproved_reviews'] ) )
|
||||
{
|
||||
$oldContainer->_unapprovedReviews = \intval( $oldContainer->_unapprovedReviews - $this->mapped('unapproved_reviews') );
|
||||
$oldContainer->_unapprovedReviews = ( $oldContainer->_unapprovedReviews > 0 ) ? \intval( $oldContainer->_unapprovedReviews - $this->mapped('unapproved_reviews') ) : 0;
|
||||
}
|
||||
|
||||
/* Make a link */
|
||||
@@ -2109,7 +2130,7 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
if ( isset( static::$commentClass ) and $container->_unapprovedComments !== NULL and isset( static::$databaseColumnMap['unapproved_comments'] ) )
|
||||
{
|
||||
$container->_unapprovedComments = ( $container->_unapprovedComments + $this->mapped('unapproved_comments') );
|
||||
$container->_unapprovedComments = ( $container->_unapprovedComments >= 0 ) ? ( $container->_unapprovedItems + $this->mapped('unapproved_comments') ) : 0;
|
||||
}
|
||||
if ( isset( static::$reviewClass ) and $this->container()->_unapprovedReviews !== NULL and isset( static::$databaseColumnMap['unapproved_reviews'] ) )
|
||||
{
|
||||
@@ -2132,7 +2153,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Item AND ( isset( static::$commentClass ) OR isset( static::$reviewClass ) ) )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired ) ? $this->firstComment() : $this );
|
||||
\IPS\Content\Search\Index::i()->index( ( static::$firstCommentRequired and $this->firstComment() ) ? $this->firstComment() : $this );
|
||||
\IPS\Content\Search\Index::i()->indexSingleItem( $this );
|
||||
}
|
||||
else
|
||||
@@ -2159,9 +2180,6 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$link->markRead();
|
||||
}
|
||||
|
||||
/* Remove from Hive */
|
||||
\IPS\core\Hive::i()->removeContent( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2732,7 +2750,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
if ( $this->container()->_comments !== NULL )
|
||||
{
|
||||
$this->container()->setLastComment();
|
||||
$this->container()->setLastComment( null, $this );
|
||||
$this->container()->save();
|
||||
}
|
||||
|
||||
@@ -2848,7 +2866,7 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
if ( $this->container()->_unapprovedComments !== NULL )
|
||||
{
|
||||
$this->container()->_unapprovedComments = ( $this->container()->_unapprovedComments - $this->mapped('unapproved_comments') );
|
||||
$this->container()->_unapprovedComments = ( $this->container()->_unapprovedComments > 0 ) ? ( $this->container()->_unapprovedComments - $this->mapped('unapproved_comments') ) : 0;
|
||||
}
|
||||
$this->container()->save();
|
||||
} catch ( \BadMethodCallException $e ) {}
|
||||
@@ -3038,8 +3056,6 @@ abstract class _Item extends \IPS\Content
|
||||
$setComment = FALSE;
|
||||
if ( isset( static::$commentClass ) and static::$firstCommentRequired )
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
|
||||
if ( isset( static::$databaseColumnMap['first_comment_id'] ) AND $comment = $this->firstComment() )
|
||||
{
|
||||
$comment->changeAuthor( $newAuthor, $log );
|
||||
@@ -3051,7 +3067,7 @@ abstract class _Item extends \IPS\Content
|
||||
/* Update container, but don't bother if we just updated the comment because it will have triggered the container to update */
|
||||
if ( !$setComment AND $container = $this->containerWrapper() )
|
||||
{
|
||||
$container->setLastComment();
|
||||
$container->setLastComment( updatedItem: $this );
|
||||
$container->setLastReview();
|
||||
$container->save();
|
||||
}
|
||||
@@ -3159,11 +3175,16 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$containerWhere = array_merge( $containerWhere, $value );
|
||||
unset( $where[ $key ] );
|
||||
|
||||
/* $containerWhere is used for exclusion purposes now,
|
||||
so we leave this as part of the where condition as well. */
|
||||
$where = array_merge( $where, $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Exclude hidden items */
|
||||
$includeAdditionalApprovalClauses = true;
|
||||
if( $includeHiddenItems === \IPS\Content\Hideable::FILTER_AUTOMATIC )
|
||||
{
|
||||
$containersTheUserCanViewHiddenItemsIn = static::canViewHiddenItemsContainers( $member );
|
||||
@@ -3199,6 +3220,8 @@ abstract class _Item extends \IPS\Content
|
||||
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}=1" );
|
||||
}
|
||||
|
||||
$includeAdditionalApprovalClauses = false;
|
||||
}
|
||||
elseif ( \in_array( 'IPS\Content\Hideable', class_implements( \get_called_class() ) ) and $includeHiddenItems !== \IPS\Content\Hideable::FILTER_SHOW_HIDDEN )
|
||||
{
|
||||
@@ -3226,6 +3249,8 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$where[] = array( "{$col}=1" );
|
||||
}
|
||||
|
||||
$includeAdditionalApprovalClauses = false;
|
||||
}
|
||||
elseif ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
@@ -3247,6 +3272,8 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$where[] = array( "{$col}=0" );
|
||||
}
|
||||
|
||||
$includeAdditionalApprovalClauses = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3274,17 +3301,21 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
}
|
||||
|
||||
/* No matter if we can or cannot view hidden items, we do not want these to show: -2 is queued for deletion and -3 is posted before register */
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}!=-2 AND {$col} !=-3" );
|
||||
}
|
||||
else if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['approved'];
|
||||
$where[] = array( "{$col}!=-2 AND {$col}!=-3" );
|
||||
}
|
||||
/* This only makes sense if we have not already filtered by a single value */
|
||||
if( $includeAdditionalApprovalClauses )
|
||||
{
|
||||
/* No matter if we can or cannot view hidden items, we do not want these to show: -2 is queued for deletion and -3 is posted before register */
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}!=-2 AND {$col} !=-3" );
|
||||
}
|
||||
else if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['approved'];
|
||||
$where[] = array( "{$col}!=-2 AND {$col}!=-3" );
|
||||
}
|
||||
}
|
||||
|
||||
/* Future items? */
|
||||
if ( \in_array( 'IPS\Content\FuturePublishing', class_implements( \get_called_class() ) ) )
|
||||
@@ -3321,18 +3352,28 @@ abstract class _Item extends \IPS\Content
|
||||
$categories = array();
|
||||
$lookupKey = md5( $containerClass::$permApp . $containerClass::$permType . $permissionKey . json_encode( $member->groups ) );
|
||||
|
||||
/* EME: Switch to use exclusion instead of inclusion */
|
||||
if( !isset( static::$permissionSelect[ $lookupKey ] ) )
|
||||
{
|
||||
static::$permissionSelect[ $lookupKey ] = array();
|
||||
$permQuery = \IPS\Db::i()->select( 'perm_type_id', 'core_permission_index', array( "core_permission_index.app='" . $containerClass::$permApp . "' AND core_permission_index.perm_type='" . $containerClass::$permType . "' AND (" . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], $member->permissionArray() ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . "='*' )" ) );
|
||||
|
||||
$permQueryJoinContainer = (bool) ( \count( $containerWhere ) );
|
||||
$clubWhere = "";
|
||||
|
||||
/* If we cannot access clubs, skip them */
|
||||
if ( \IPS\IPS::classUsesTrait( $containerClass, 'IPS\Content\ClubContainer' ) AND !$member->canAccessModule( \IPS\Application\Module::get( 'core', 'clubs', 'front' ) ) )
|
||||
{
|
||||
$containerWhere[] = array( $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::clubIdColumn() . ' IS NULL' );
|
||||
$permQueryWhere = array( "core_permission_index.app='" . $containerClass::$permApp . "' AND core_permission_index.perm_type='" . $containerClass::$permType . "' AND (" . $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::clubIdColumn() . " IS NOT NULL OR !(" . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], $member->permissionArray() ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . "='*' ))" );
|
||||
$permQueryJoinContainer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$permQueryWhere = array( "core_permission_index.app='" . $containerClass::$permApp . "' AND core_permission_index.perm_type='" . $containerClass::$permType . "' AND !(" . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], $member->permissionArray() ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . "='*' )" );
|
||||
}
|
||||
|
||||
$permQuery = \IPS\Db::i()->select( 'perm_type_id', 'core_permission_index', $permQueryWhere );
|
||||
|
||||
if ( \count( $containerWhere ) )
|
||||
if( $permQueryJoinContainer )
|
||||
{
|
||||
$permQuery->join( $containerClass::$databaseTable, array_merge( $containerWhere, array( 'core_permission_index.perm_type_id=' . $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::$databaseColumnId ) ), 'STRAIGHT_JOIN' );
|
||||
}
|
||||
@@ -3347,11 +3388,7 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
if( \count( $categories ) )
|
||||
{
|
||||
$where[] = array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['container'] . ' IN(' . implode( ',', $categories ) . ')' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['container'] . '=0' );
|
||||
$where[] = array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['container'] . ' NOT IN (' . implode( ',', $categories ) . ')' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3363,8 +3400,10 @@ abstract class _Item extends \IPS\Content
|
||||
$select = \IPS\Db::i()->select( 'COUNT(*) as cnt', static::$databaseTable, $where, NULL, NULL, $groupBy, NULL, $queryFlags );
|
||||
if ( $joinContainer AND isset( static::$containerNodeClass ) )
|
||||
{
|
||||
/* EME: Removed the $containerWhere from the join because it is now in the where clause.
|
||||
We are now using $containerWhere to exclude forums that shouldn't be visible. */
|
||||
$containerClass = static::$containerNodeClass;
|
||||
$select->join( $containerClass::$databaseTable, array_merge( $containerWhere, array( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['container'] . '=' . $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::$databaseColumnId ) ) );
|
||||
$select->join( $containerClass::$databaseTable, array( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['container'] . '=' . $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::$databaseColumnId ) );
|
||||
}
|
||||
if ( $joinComments )
|
||||
{
|
||||
@@ -4290,48 +4329,7 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
/* If we have a cutoff with no offset explicitly defined, we should not automatically generate one for pagination since our results will be limited */
|
||||
$offset = ( $cutoff and $offset === NULL ) ? 0 : ( $offset !== NULL ? $offset : ( ( $_pageValue - 1 ) * $limit ) );
|
||||
$ids = array();
|
||||
|
||||
/* If we are ordering by (date) DESC/ASC and only fetching one result, we are trying to find the first/last comment. This can be sped up greatly by selecting MIN(id) or MAX(id) instead of running the normal query, which often results in a filesort */
|
||||
if( $limit == 1 AND !$offset AND mb_strtolower( $order ) == mb_strtolower( $class::$databasePrefix . $class::$databaseColumnMap['date'] . ' desc' ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$where = array( array( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . '=(?)', $class::db()->select( 'MAX(' . $class::$databasePrefix . $class::$databaseColumnId . ')', $class::$databaseTable, $where ) ) );
|
||||
$offset = 0;
|
||||
$order = NULL;
|
||||
}
|
||||
catch( \UnderflowException $e ){}
|
||||
}
|
||||
elseif( $limit == 1 AND !$offset AND mb_strtolower( $order ) == mb_strtolower( $class::$databasePrefix . $class::$databaseColumnMap['date'] . ' asc' ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$where = array( array( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . '=(?)', $class::db()->select( 'MIN(' . $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . ')', $class::$databaseTable, $where ) ) );
|
||||
$offset = 0;
|
||||
$order = NULL;
|
||||
}
|
||||
catch( \UnderflowException $e ){}
|
||||
}
|
||||
/* Large topics, private messages, etc. benefit greatly from splitting the query into two queries */
|
||||
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 */
|
||||
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;
|
||||
$order = NULL;
|
||||
}
|
||||
|
||||
/* Construct the query */
|
||||
$results = array();
|
||||
$bits = \IPS\Db::SELECT_MULTIDIMENSIONAL_JOINS;
|
||||
@@ -4369,21 +4367,7 @@ abstract class _Item extends \IPS\Content
|
||||
$results[ $result->$commentIdColumn ] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we used two queries, ensure $result is sorted by the order of $ids */
|
||||
if ( \count( $ids ) )
|
||||
{
|
||||
$newResults = array();
|
||||
|
||||
foreach( $ids as $k => $v )
|
||||
{
|
||||
$newResults[ $k ] = $results[ $k ];
|
||||
}
|
||||
|
||||
$results = $newResults;
|
||||
unset($newResults);
|
||||
}
|
||||
|
||||
|
||||
/* Get the reputation stuff now so we don 't have to do lots of queries later */
|
||||
if ( \IPS\Settings::i()->reputation_enabled AND \IPS\IPS::classUsesTrait( $class, 'IPS\Content\Reactable' ) AND \count( $results ) AND \IPS\Dispatcher::hasInstance() )
|
||||
{
|
||||
@@ -4983,7 +4967,7 @@ abstract class _Item extends \IPS\Content
|
||||
$app = static::$application ?? null;
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$dataLayer = array(
|
||||
'author_id' => \IPS\core\DataLayer::i()->getSsoId( $this->author()->member_id ),
|
||||
'author_id' => $this->author()->member_id,
|
||||
'author_name' => $this->author()->real_name ?: null,
|
||||
'content_age' => $this->mapped( 'date' ) ? \intval( floor( ( time() - $this->mapped( 'date' ) ) / 86400 ) ) : null,
|
||||
'content_container_id' => null,
|
||||
@@ -5100,7 +5084,7 @@ abstract class _Item extends \IPS\Content
|
||||
$return['guest_name'] = new \IPS\Helpers\Form\Text( 'guest_name', NULL, FALSE, array( 'minLength' => \IPS\Settings::i()->min_user_name_length, 'maxLength' => \IPS\Settings::i()->max_user_name_length, 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('comment_guest_name') ) );
|
||||
}
|
||||
}
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' and \IPS\Settings::i()->guest_captcha )
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' )
|
||||
{
|
||||
$return['captcha'] = new \IPS\Helpers\Form\Captcha;
|
||||
}
|
||||
@@ -5168,7 +5152,7 @@ abstract class _Item extends \IPS\Content
|
||||
'follow_area' => $followArea,
|
||||
'follow_rel_id' => $this->$idColumn,
|
||||
'follow_member_id' => \IPS\Member::loggedIn()->member_id,
|
||||
'follow_is_anon' => 0,
|
||||
'follow_is_anon' => isset( $values[ 'post_anonymously' ] ) ? (bool) $values[ 'post_anonymously' ] : 0,
|
||||
'follow_added' => time() + 1, // Make sure streams show follows after content is created
|
||||
'follow_notify_do' => 1,
|
||||
'follow_notify_meta' => '',
|
||||
@@ -5192,13 +5176,15 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
if ( static::$firstCommentRequired and !$comment->isFirst() )
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
|
||||
\IPS\Content\Search\Index::i()->index( $this->firstComment() );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this );
|
||||
/* The first comment might have been pruned, so check the time frame and reindex if necessary */
|
||||
if( \IPS\Settings::i()->search_method == 'mysql' and \IPS\Settings::i()->search_index_timeframe )
|
||||
{
|
||||
$cutoff = \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->search_index_timeframe . 'D' ) )->getTimestamp();
|
||||
if( $this->mapped( 'date' ) <= $cutoff )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this->firstComment() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5241,7 +5227,7 @@ abstract class _Item extends \IPS\Content
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'guest_name', NULL, FALSE, array( 'minLength' => \IPS\Settings::i()->min_user_name_length, 'maxLength' => \IPS\Settings::i()->max_user_name_length, 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('comment_guest_name') ) ) );
|
||||
}
|
||||
}
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' and \IPS\Settings::i()->guest_captcha )
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Captcha );
|
||||
}
|
||||
@@ -5488,7 +5474,7 @@ abstract class _Item extends \IPS\Content
|
||||
* Can Comment/Review
|
||||
*
|
||||
* @param string $type Type
|
||||
* @param \IPS\Member\NULL $member The member (NULL for currently logged in member)
|
||||
* @param \IPS\Member|NULL $member The member (NULL for currently logged in member)
|
||||
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will return TRUE if "Post Before Registering" feature is enabled
|
||||
* @return bool
|
||||
*/
|
||||
@@ -5924,9 +5910,11 @@ abstract class _Item extends \IPS\Content
|
||||
/**
|
||||
* Resync last comment
|
||||
*
|
||||
* @param Comment|null $comment The comment
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resyncLastComment()
|
||||
public function resyncLastComment( \IPS\Content\Comment $comment = NULL )
|
||||
{
|
||||
if( !isset( static::$commentClass ) )
|
||||
{
|
||||
@@ -5942,6 +5930,12 @@ abstract class _Item extends \IPS\Content
|
||||
$resync = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this comment isn't newer than the cached value, we don't need to do anything */
|
||||
if( $resync AND $comment !== NULL AND isset( static::$databaseColumnMap['last_comment'] ) AND $comment->mapped('date') < $this->mapped('last_comment' ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $resync )
|
||||
{
|
||||
@@ -5955,7 +5949,6 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
throw new \UnderflowException;
|
||||
}
|
||||
|
||||
if ( isset( static::$databaseColumnMap['last_comment'] ) )
|
||||
{
|
||||
$lastCommentField = static::$databaseColumnMap['last_comment'];
|
||||
@@ -6044,7 +6037,7 @@ abstract class _Item extends \IPS\Content
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resyncLastReview()
|
||||
public function resyncLastReview( \IPS\Content\Comment $comment = NULL )
|
||||
{
|
||||
if( !isset( static::$reviewClass ) )
|
||||
{
|
||||
@@ -6060,6 +6053,12 @@ abstract class _Item extends \IPS\Content
|
||||
$resync = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this comment isn't newer than the cached value, we don't need to do anything */
|
||||
if( $resync AND $comment !== NULL AND isset( static::$databaseColumnMap['last_review'] ) AND $comment->mapped('date') < static::$databaseColumnMap['last_review'] )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $resync )
|
||||
{
|
||||
@@ -6152,19 +6151,12 @@ abstract class _Item extends \IPS\Content
|
||||
* @param bool $includeItems If TRUE, items will be included (this should usually be true)
|
||||
* @param bool $includeComments If TRUE, comments will be included
|
||||
* @param bool $includeReviews If TRUE, reviews will be included
|
||||
* @param int $depth Used to keep track of current depth to avoid going too deep
|
||||
* @return int|NULL|string When depth exceeds 10, will return "NULL" and initial call will return something like "100+"
|
||||
* @note This method may return something like "100+" if it has lots of children to avoid exahusting memory. It is intended only for display use
|
||||
* @param int $depth Used to keep track of current depth
|
||||
* @return int
|
||||
* @note This method includes counts of hidden and unapproved content items as well
|
||||
*/
|
||||
public static function contentCount( \IPS\Node\Model $container, $includeItems=TRUE, $includeComments=FALSE, $includeReviews=FALSE, $depth=0 )
|
||||
{
|
||||
/* Are we in too deep? */
|
||||
if ( $depth > 3 )
|
||||
{
|
||||
return '+';
|
||||
}
|
||||
|
||||
/* Generate a key */
|
||||
$_key = md5( \get_class( $container ) . $container->_id );
|
||||
|
||||
@@ -6256,19 +6248,10 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
|
||||
/* Add Children */
|
||||
$childDepth = $depth++;
|
||||
$childDepth = ++$depth;
|
||||
foreach ( $container->children() as $child )
|
||||
{
|
||||
$toAdd = static::contentCount( $child, $includeItems, $includeComments, $includeReviews, $childDepth );
|
||||
if ( \is_string( $toAdd ) )
|
||||
{
|
||||
return $count . '+';
|
||||
}
|
||||
else
|
||||
{
|
||||
$count += $toAdd;
|
||||
}
|
||||
|
||||
$count += static::contentCount( $child, $includeItems, $includeComments, $includeReviews, $childDepth );
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
@@ -6413,15 +6396,32 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
$anonymous ? (bool) \IPS\Member::loggedIn()->modPermissions() : FALSE;
|
||||
|
||||
$lastDate = NULL;
|
||||
|
||||
$lowestCommentDate = 0;
|
||||
$highestCommentDate = $this->isLastPage() ? time() : 0; // If we're on the last page, we want any events from the earliest comment to now, not just the last post
|
||||
|
||||
/* Get the lowest and highest possible timestamps from $comments */
|
||||
foreach( $comments as $comment )
|
||||
{
|
||||
$commentDate = $comment->mapped( 'date' );
|
||||
|
||||
if( $lowestCommentDate === 0 || $commentDate < $lowestCommentDate )
|
||||
{
|
||||
$lowestCommentDate = $commentDate;
|
||||
}
|
||||
|
||||
if( $commentDate > $highestCommentDate )
|
||||
{
|
||||
$highestCommentDate = $commentDate;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get moderation items */
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$moderationItems = array();
|
||||
|
||||
if ( $this->showCommentMeta('moderation') )
|
||||
{
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_moderator_logs', array('class=? AND item_id=?', \get_class( $this ), $this->$idColumn), 'ctime ASC' ) as $row )
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_moderator_logs', array('class=? AND ( ctime BETWEEN ? AND ? ) AND item_id=?', \get_class( $this ), $lowestCommentDate - 1, $highestCommentDate + 1, $this->$idColumn), 'ctime ASC' ) as $row )
|
||||
{
|
||||
if ( \in_array( $row['lang_key'], array('modlog__action_unfeature', 'modlog__action_feature', 'modlog__action_unlock', 'modlog__action_lock', 'modlog__action_unpin', 'modlog__action_pin', 'modlog__item_edit', 'modlog__comment_edit_title') ) )
|
||||
{
|
||||
@@ -6447,7 +6447,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$modActionDate = $data['ctime'];
|
||||
|
||||
if ( ( $modActionDate > $commentDate and $modActionDate < $nextCommentDate ) or ( $modActionDate > $commentDate and !$next ) )
|
||||
if ( ( $modActionDate >= $commentDate and $modActionDate <= $nextCommentDate ) or ( $modActionDate >= $commentDate and !$next ) )
|
||||
{
|
||||
$commentDate = $modActionDate;
|
||||
$blurb = NULL;
|
||||
@@ -6456,7 +6456,15 @@ abstract class _Item extends \IPS\Content
|
||||
/* 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' ) ) )
|
||||
{
|
||||
$note = array_keys( json_decode( $data['note'], TRUE ) );
|
||||
$modNotes = json_decode( $data['note'], TRUE );
|
||||
if( $modNotes )
|
||||
{
|
||||
$note = array_keys( $modNotes );
|
||||
}
|
||||
else
|
||||
{
|
||||
$note = array();
|
||||
}
|
||||
|
||||
if( ( \count( $note ) == 4 && $data['lang_key'] == 'modlog__item_edit' ) || ( \count( $note ) == 3 && $data['lang_key'] == 'modlog__comment_edit_title' ) )
|
||||
{
|
||||
@@ -7338,6 +7346,11 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$container = $this->container();
|
||||
|
||||
if( $container->_unapprovedItems !== NULL and $approving )
|
||||
{
|
||||
$container->_unapprovedItems = ( $container->_unapprovedItems > 0 ) ? ( $container->_unapprovedItems - 1 ) : 0;
|
||||
}
|
||||
|
||||
if ( ! $this->isFutureDate() )
|
||||
{
|
||||
$container->resetCommentCounts();
|
||||
@@ -7859,7 +7872,7 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
|
||||
$unreadCount = \IPS\Db::i()->select(
|
||||
'COUNT(*) as count',
|
||||
'COUNT(' . static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnId . ') as count',
|
||||
static::$databaseTable,
|
||||
$whereClause
|
||||
)->first();
|
||||
@@ -8171,7 +8184,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
if ( \IPS\Settings::i()->tags_alphabetical )
|
||||
{
|
||||
natcasesort( $this->tags['tags'] );
|
||||
sort( $this->tags['tags'], SORT_NATURAL | SORT_FLAG_CASE );
|
||||
}
|
||||
|
||||
return $this->tags['tags'];
|
||||
@@ -8212,10 +8225,11 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$set = array( $set );
|
||||
}
|
||||
|
||||
|
||||
$insert = [];
|
||||
foreach ( $set as $key => $tag )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_tags', array(
|
||||
$insert[] = array(
|
||||
'tag_aai_lookup' => $aaiLookup,
|
||||
'tag_aap_lookup' => $aapLookup,
|
||||
'tag_meta_app' => static::$application,
|
||||
@@ -8226,7 +8240,7 @@ abstract class _Item extends \IPS\Content
|
||||
'tag_added' => time(),
|
||||
'tag_prefix' => $key === 'prefix',
|
||||
'tag_text' => $tag
|
||||
), TRUE );
|
||||
);
|
||||
|
||||
if ( $key === 'prefix' )
|
||||
{
|
||||
@@ -8237,6 +8251,11 @@ abstract class _Item extends \IPS\Content
|
||||
$this->tags['tags'][] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
if( \count( $insert ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_tags', $insert, TRUE );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->insert( 'core_tags_cache', array(
|
||||
'tag_cache_key' => $aaiLookup,
|
||||
@@ -8260,12 +8279,6 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
}
|
||||
|
||||
/* Add to search index */
|
||||
if ( $this instanceof \IPS\Content\Searchable )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this );
|
||||
}
|
||||
|
||||
/* Callback once tags are updated */
|
||||
$this->processAfterTagUpdate();
|
||||
}
|
||||
@@ -8670,6 +8683,13 @@ abstract class _Item extends \IPS\Content
|
||||
$this->$future = 0;
|
||||
}
|
||||
|
||||
$dateColumn = static::$databaseColumnMap['date'];
|
||||
if ( \is_array( $dateColumn ) )
|
||||
{
|
||||
$dateColumn = array_pop( $dateColumn );
|
||||
}
|
||||
$this->$dateColumn = time();
|
||||
|
||||
if ( isset( static::$databaseColumnMap['last_comment'] ) or isset( static::$databaseColumnMap['last_review'] ) )
|
||||
{
|
||||
$lastCommentField = static::$databaseColumnMap['last_comment'];
|
||||
@@ -9684,4 +9704,30 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output for API
|
||||
*
|
||||
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @return array
|
||||
* @apiresponse int id ID number
|
||||
* @apiresponse string|null title Title ( if available )
|
||||
* @apiresponse \IPS\Node\Model container Container
|
||||
* @apiresponse \IPS\Member author The member that created the item
|
||||
* @apiresponse datetime date Date
|
||||
* @apiresponse string content Content
|
||||
* @apiresponse string url URL
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL )
|
||||
{
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
'title' => $this->title ? $this->title : NULL,
|
||||
'container' => $this->containerWrapper()?->apiOutput( $authorizedMember ),
|
||||
'author' => $this->author()->apiOutput( $authorizedMember ),
|
||||
'date' => \IPS\DateTime::ts( $this->date )->rfc3339(),
|
||||
'content' => \IPS\Text\Parser::removeLazyLoad( $this->content() ),
|
||||
'url' => (string) $this->url()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,12 @@ trait ItemTopic
|
||||
*/
|
||||
public function syncTopic()
|
||||
{
|
||||
/* If this item is pending deletion, do nothing */
|
||||
if( $this->hidden() == -2 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$column = static::topicIdColumn();
|
||||
$containercolumn = static::containerForumIdColumn();
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ trait Reactable
|
||||
\IPS\cloud\Realtime::i()->publishEvent( 'reaction-count', array('count' => $this->_reactionCount) );
|
||||
}
|
||||
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Realtime::i()->isEnabled('trending') )
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Application::trendingIsEnabled() )
|
||||
{
|
||||
/* We need to make sure we're using the item */
|
||||
$item = ( ! $this instanceof \IPS\Content\Item ) ? $this->item() : $this;
|
||||
|
||||
@@ -167,7 +167,7 @@ trait Recognizable
|
||||
public function recognizedBlurb(): array
|
||||
{
|
||||
$return = [ 'message' => NULL ];
|
||||
$return['main'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_main', FALSE, [ 'htmlsprintf' => [ $this::$title, $this->recognized->_given_by->link() ] ] );
|
||||
$return['main'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_main', FALSE, [ 'htmlsprintf' => [ \IPS\Member::loggedIn()->language()->addToStack( $this::$title . '_lc' ), $this->recognized->_given_by->link() ] ] );
|
||||
if ( $this->recognized->badge and $this->recognized->points )
|
||||
{
|
||||
if ( $this->author()->member_id === \IPS\Member::loggedIn()->member_id )
|
||||
|
||||
@@ -186,6 +186,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$existingData = NULL;
|
||||
$existingIndexId = NULL;
|
||||
$resetLastComment = FALSE;
|
||||
$firstCommentId = $this->getFirstCommentId( $object );
|
||||
|
||||
try
|
||||
{
|
||||
@@ -198,7 +199,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
|
||||
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
|
||||
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
|
||||
{
|
||||
/* We do not allow hidden or needing approval comments to become flagged as the last comment as this means users without hidden view permission never see the item in an item only stream */
|
||||
$indexData['index_is_last_comment'] = false;
|
||||
@@ -223,7 +224,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
if ( $resetLastComment )
|
||||
{
|
||||
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
|
||||
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'], null, $firstCommentId );
|
||||
}
|
||||
|
||||
/* Views / Comments / Reviews */
|
||||
@@ -415,9 +416,9 @@ class _Index extends \IPS\Content\Search\Index
|
||||
* Retrieve the search ID for an item
|
||||
*
|
||||
* @param \IPS\Content\Searchable $object Item to add
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndexId( \IPS\Content\Searchable $object )
|
||||
public function getIndexId( \IPS\Content\Searchable $object ): string
|
||||
{
|
||||
$databaseColumnId = $object::$databaseColumnId;
|
||||
return \strtolower( str_replace( '\\', '_', \substr( \get_class( $object ), 4 ) ) ) . '-' . $object->$databaseColumnId;
|
||||
@@ -455,7 +456,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$classes[] = $itemClass::$reviewClass;
|
||||
}
|
||||
|
||||
$this->resetLastComment( $classes, $object->item()->$itemIdColumn, $object->$idColumn );
|
||||
$this->resetLastComment( $classes, $object->item()->$itemIdColumn, $object->$idColumn, $this->getFirstCommentId( $object ) );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
@@ -858,16 +859,17 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$this->init();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the last comment flag in any given class/index_item_id
|
||||
*
|
||||
* @param array $classes The classes (when first post is required, this is typically just \IPS\forums\Topic\Post but for others, it will be both item and comment classes)
|
||||
* @param int|NULL $indexItemId The index item ID
|
||||
* @param int|NULL $ignoreId ID to ignore because it is being removed
|
||||
* @param int|null $firstCommentId The first comment in this item, used only if $firstCommentRequired
|
||||
* @return void
|
||||
*/
|
||||
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL )
|
||||
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL, $firstCommentId = NULL )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -964,7 +966,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
),
|
||||
array(
|
||||
'term' => array(
|
||||
'index_object_id' => $indexItemId
|
||||
'index_object_id' => ( $firstCommentId ?? $indexItemId )
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1107,7 +1107,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$filters = $this->filters;
|
||||
|
||||
/* Set our conditions for this search */
|
||||
if ( $term !== NULL or $tags !== NULL )
|
||||
if ( $term !== NULL or ( \IPS\Settings::i()->tags_enabled AND $tags !== NULL ) )
|
||||
{
|
||||
$searchConditions = array();
|
||||
$titleField = \IPS\Settings::i()->search_title_boost ? ( 'index_title^' . \intval( \IPS\Settings::i()->search_title_boost ) ) : 'index_title';
|
||||
@@ -1165,7 +1165,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
/* Build the condition for the tags */
|
||||
if ( $tags !== NULL )
|
||||
if ( \IPS\Settings::i()->tags_enabled and $tags !== NULL )
|
||||
{
|
||||
$searchConditions[] = array(
|
||||
'bool' => array(
|
||||
|
||||
@@ -250,7 +250,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
}
|
||||
|
||||
/* If this comment is hidden, don't actually mark as the last comment as that will cause this item to be hidden in search if we are getting only the last comment. */
|
||||
if ( $isLastComment AND $object->hidden() )
|
||||
if ( $isLastComment AND $object->hidden() AND !$object->isFirst() )
|
||||
{
|
||||
$isLastComment = 0;
|
||||
}
|
||||
@@ -432,6 +432,41 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
'index_item_solved' => ( $itemClass and \IPS\IPS::classUsesTrait( $itemClass, 'IPS\Content\Solvable' ) ) ? ( $object->item()->mapped('solved_comment_id') == $object->$idColumn ) ? 1 : 0 : NULL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object ID of the first comment.
|
||||
* Used for posts, where the index_object_id is NOT the topic ID
|
||||
*
|
||||
* @param \IPS\Content\Searchable $object
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getFirstCommentId( \IPS\Content\Searchable $object ) : ?int
|
||||
{
|
||||
if( $object instanceof \IPS\Content\Comment )
|
||||
{
|
||||
$itemClass = $object::$itemClass;
|
||||
if( $itemClass::$firstCommentRequired )
|
||||
{
|
||||
if ( $object->isFirst() )
|
||||
{
|
||||
$column = $object::$databaseColumnId;
|
||||
return $object->$column;
|
||||
}
|
||||
else if ( isset( $itemClass::$databaseColumnMap['first_comment_id'] ) )
|
||||
{
|
||||
$column = $itemClass::$databaseColumnMap['first_comment_id'];
|
||||
return $object->item()->$column;
|
||||
}
|
||||
else
|
||||
{
|
||||
$column = $itemClass::$databaseColumnId;
|
||||
return $object->item()->firstComment()->$column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index an item
|
||||
@@ -455,9 +490,9 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
* Retrieve the search ID for an item
|
||||
*
|
||||
* @param \IPS\Content\Searchable $object Item to add
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getIndexId( \IPS\Content\Searchable $object );
|
||||
abstract public function getIndexId( \IPS\Content\Searchable $object ): string;
|
||||
|
||||
/**
|
||||
* Remove item
|
||||
@@ -555,9 +590,10 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
* @param string $class The class
|
||||
* @param int|NULL $indexItemId The index item ID
|
||||
* @param int|NULL $ignoreId ID to ignore because it is being removed
|
||||
* @param int|null $firstCommentId The first comment in this item, used only if $firstCommentRequired
|
||||
* @return void
|
||||
*/
|
||||
abstract public function resetLastComment( $class, $indexItemId, $ignoreId = NULL );
|
||||
abstract public function resetLastComment( $class, $indexItemId, $ignoreId = NULL, $firstCommentId = NULL );
|
||||
|
||||
/**
|
||||
* Given a list of item index IDs, return the ones that a given member has participated in
|
||||
|
||||
@@ -73,6 +73,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$existingIndexId = NULL;
|
||||
$resetLastComment = FALSE;
|
||||
$newIndexId = NULL;
|
||||
$firstCommentId = $this->getFirstCommentId( $object );
|
||||
|
||||
try
|
||||
{
|
||||
@@ -91,7 +92,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
unset( $indexData['index_prefix'] );
|
||||
}
|
||||
|
||||
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
|
||||
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
|
||||
{
|
||||
/* We do not allow hidden or needing approval comments to become flagged as the last comment as this means users without hidden view permission never see the item in an item only stream */
|
||||
$indexData['index_is_last_comment'] = 0;
|
||||
@@ -129,6 +130,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
}
|
||||
|
||||
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_is_last_comment=1', $indexData['index_item_id'] ) );
|
||||
|
||||
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $indexData['index_date_updated'], 'index_date_commented' => $indexData['index_date_commented'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_object_id=?', $indexData['index_item_id'], ( $firstCommentId ?? $indexData['index_item_id'] ) ) );
|
||||
}
|
||||
|
||||
if ( $existingData !== NULL and ( $indexData['index_class'] == $existingData['index_class'] and $indexData['index_object_id'] == $existingData['index_object_id'] ) )
|
||||
@@ -214,7 +217,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
if ( $resetLastComment )
|
||||
{
|
||||
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
|
||||
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'], null, $firstCommentId );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,9 +227,9 @@ class _Index extends \IPS\Content\Search\Index
|
||||
* Retrieve the search ID for an item
|
||||
*
|
||||
* @param \IPS\Content\Searchable $object Item to add
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function getIndexId( \IPS\Content\Searchable $object )
|
||||
public function getIndexId( \IPS\Content\Searchable $object ): string
|
||||
{
|
||||
$databaseColumnId = $object::$databaseColumnId;
|
||||
return \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_object_id=?', \get_class( $object ),$object->$databaseColumnId ) )->first();
|
||||
@@ -266,7 +269,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
try
|
||||
{
|
||||
$this->resetLastComment( $classes, $object->mapped('item'), $object->$idColumn );
|
||||
$this->resetLastComment( $classes, $object->mapped('item'), $object->$idColumn, $this->getFirstCommentId( $object ) );
|
||||
}
|
||||
catch( \Exception $ex ) { }
|
||||
|
||||
@@ -583,14 +586,15 @@ class _Index extends \IPS\Content\Search\Index
|
||||
* @param array $classes The classes (when first post is required, this is typically just \IPS\forums\Topic\Post but for others, it will be both item and comment classes)
|
||||
* @param int|NULL $indexItemId The index item ID
|
||||
* @param int|NULL $ignoreId ID to ignore because it is being removed
|
||||
* @param int|null $firstCommentId The first comment in this item, used only if $firstCommentRequired
|
||||
* @return void
|
||||
*/
|
||||
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL )
|
||||
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL, $firstCommentId = NULL )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_is_last_comment=1', $indexItemId ) );
|
||||
try
|
||||
{
|
||||
$latest = \IPS\Db::i()->select( 'index_object_id, index_date_updated, index_class', 'core_search_index', array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_hidden=0', $indexItemId ), 'index_date_created DESC', array( 0, 1 ) )->first();
|
||||
$latest = \IPS\Db::i()->select( 'index_object_id, index_date_updated, index_class, index_date_commented', 'core_search_index', array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_hidden=0', $indexItemId ), 'index_date_created DESC', array( 0, 1 ) )->first();
|
||||
}
|
||||
catch( \Exception )
|
||||
{
|
||||
@@ -619,7 +623,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 1 ), array( 'index_class=? AND index_object_id=?', $latest['index_class'], $latest['index_object_id'] ) );
|
||||
|
||||
/* Now reset the item index with the latest comment time */
|
||||
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $latest['index_date_updated'], 'index_date_commented' => $latest['index_date_commented'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_object_id=?', $indexItemId, $indexItemId ) );
|
||||
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $latest['index_date_updated'], 'index_date_commented' => $latest['index_date_commented'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_object_id=?', $indexItemId, ( $firstCommentId ?? $indexItemId ) ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -172,19 +172,19 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
$this->joins[] = array( 'from' => $class::$databaseTable, 'where' => $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . '=main.index_item_id' );
|
||||
|
||||
if ( $filter->minimumComments )
|
||||
if ( $filter->minimumComments AND isset( $class::$databaseColumnMap['num_comments'] ) )
|
||||
{
|
||||
$this->select[ $class::$databaseTable . '_comments' ] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_comments'];
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_comments'] . '>=' . \intval( $filter->minimumComments );
|
||||
}
|
||||
|
||||
if ( $filter->minimumReviews )
|
||||
if ( $filter->minimumReviews AND isset( $class::$databaseColumnMap['num_reviews'] ) )
|
||||
{
|
||||
$this->select[ $class::$databaseTable . '_reviews' ] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_reviews'];
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_reviews'] . '>=' . \intval( $filter->minimumReviews );
|
||||
}
|
||||
|
||||
if ( $filter->minimumViews )
|
||||
if ( $filter->minimumViews AND isset( $class::$databaseColumnMap['views'] ) )
|
||||
{
|
||||
$this->select[ $class::$databaseTable . '_views' ] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['views'];
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['views'] . '>=' . \intval( $filter->minimumViews );
|
||||
@@ -503,21 +503,24 @@ class _Query extends \IPS\Content\Search\Query
|
||||
else if ( \count( $followed ) )
|
||||
{
|
||||
/* IN is not a SIN. It's been a long day */
|
||||
$where[] = \IPS\Db::i()->in( 'index_author', $followed );
|
||||
$where[] = \IPS\Db::i()->in( 'index_author', $followed );
|
||||
}
|
||||
else
|
||||
else if ( ! $includeItems and ! $includeContainers )
|
||||
{
|
||||
/* There are no results */
|
||||
/* There are no results, and we only wanted members they followed */
|
||||
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $ex )
|
||||
{
|
||||
/* There are no results */
|
||||
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
|
||||
if ( ! $includeItems and ! $includeContainers )
|
||||
{
|
||||
/* There are no results and we only wanted members they followed */
|
||||
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Put it all together */
|
||||
if ( \count( $where ) )
|
||||
{
|
||||
@@ -905,7 +908,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$termWhere = NULL;
|
||||
|
||||
/* Do we have tags? */
|
||||
if ( $tags !== NULL )
|
||||
if ( \IPS\Settings::i()->tags_enabled AND $tags !== NULL )
|
||||
{
|
||||
$itemsOnlyTagSearch = '';
|
||||
|
||||
@@ -1001,15 +1004,39 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
|
||||
$results = iterator_to_array( \IPS\Db::i()->select( 'index_item_id, index_class', array( 'core_search_index_item_map', 'sub' ), array( array( 'index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . \IPS\Db::i()->in( 'index_class', $classes ) ) ), 'index_item_id desc', array( 0, 501 ) ) );
|
||||
|
||||
if ( \count( $results ) )
|
||||
$results = iterator_to_array( \IPS\Db::i()->select( 'index_item_id, index_class', array( 'core_search_index_item_map', 'sub' ), array( array( 'index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . \IPS\Db::i()->in( 'index_class', $classes ) ) ), 'index_item_id desc', array( 0, 801 ) ) );
|
||||
|
||||
if ( \count( $results ) == 801 )
|
||||
{
|
||||
/* Assume we have loads of matches, so do a full query */
|
||||
$subClause = array( array( 'sub.index_item_id=main.index_item_id AND index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . $this->makeEmbeddedIfClauseForSearchMapTable('sub.index_class') ) );
|
||||
$query = \IPS\Db::i()->select( 'index_item_id', array( 'core_search_index_item_map', 'sub' ), $subClause );
|
||||
$where[] = array( 'EXISTS(?)', $query );
|
||||
}
|
||||
else if ( \count( $results ) )
|
||||
{
|
||||
$classIds = array();
|
||||
foreach( $results as $result )
|
||||
{
|
||||
$classIds[ $result['index_class'] ][] = $result['index_item_id'];
|
||||
}
|
||||
|
||||
$subWhere = array();
|
||||
foreach( $classIds as $class => $ids )
|
||||
{
|
||||
$stack = array( $class );
|
||||
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$stack[] = $class::$commentClass;
|
||||
}
|
||||
|
||||
$subWhere[] = '(' . \IPS\Db::i()->in( 'index_class', $stack ) . ' and ' . \IPS\Db::i()->in( 'index_item_id', $ids ) . ')';
|
||||
}
|
||||
|
||||
/* IN is not a SIN. It's been a long day */
|
||||
$where[] = '(' . implode( ' OR ', $subWhere ) . ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
/* There are no results */
|
||||
@@ -1264,7 +1291,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
/* Set order clause */
|
||||
$order = $this->order;
|
||||
|
||||
|
||||
/* This forces MySQL to strongly hint a better index to the optimiser for MySQL 5.7+, even though sorting will not be affected */
|
||||
if ( !\IPS\CIC )
|
||||
{
|
||||
@@ -1276,7 +1303,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* If we're filtering by unread, and not using MariaDB, then force an index because MySQL tends to use the incorrect one in this scenario. */
|
||||
if ( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and $this->filterByUnread === TRUE )
|
||||
if ( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and $this->filterByUnread === TRUE and \IPS\Db::i()->server_version < 80000 )
|
||||
{
|
||||
$this->forceIndex = 'index_date_updated';
|
||||
}
|
||||
@@ -1288,7 +1315,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
if ( mb_substr( $this->order, 0, 9 ) === 'calcscore' )
|
||||
{
|
||||
/* But we can only do that if there's a term (rather than tag-only) */
|
||||
if ( $term !== NULL )
|
||||
if ( static::canUseRelevancy() and $term !== NULL )
|
||||
{
|
||||
if ( \IPS\Settings::i()->search_title_boost )
|
||||
{
|
||||
@@ -1311,7 +1338,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
. ',1.5)' // To the power of 1.5
|
||||
. ' AS calcscore';
|
||||
}
|
||||
/* So if we don't have a term, fallback to last updated */
|
||||
/* So if we don't have a term ot can't use relevancy, fallback to last updated */
|
||||
else
|
||||
{
|
||||
$order = 'index_date_updated DESC';
|
||||
@@ -1369,7 +1396,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
*/
|
||||
public function getDefaultDateCutOff()
|
||||
{
|
||||
if ( \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD and static::getTableSize() >= \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD )
|
||||
if ( \IPS\USE_MYSQL_SEARCH_OPTIMIZED_MODE )
|
||||
{
|
||||
return 'year';
|
||||
}
|
||||
@@ -1384,13 +1411,23 @@ class _Query extends \IPS\Content\Search\Query
|
||||
*/
|
||||
public function getDefaultSortMethod()
|
||||
{
|
||||
if ( \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD and static::getTableSize() >= \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD )
|
||||
if ( \IPS\USE_MYSQL_SEARCH_OPTIMIZED_MODE )
|
||||
{
|
||||
return 'newest';
|
||||
}
|
||||
|
||||
return parent::getDefaultSortMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can use relevancy?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canUseRelevancy(): bool
|
||||
{
|
||||
return ! \IPS\USE_MYSQL_SEARCH_OPTIMIZED_MODE;
|
||||
}
|
||||
|
||||
protected static $tableCount = NULL;
|
||||
|
||||
@@ -1403,9 +1440,27 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
if ( static::$tableCount === NULL )
|
||||
{
|
||||
/* Try and be more efficient */
|
||||
try
|
||||
{
|
||||
if( $result = \IPS\Db::i()->query( "SELECT TABLE_ROWS as _size FROM `information_schema`.`TABLES` WHERE TABLE_SCHEMA = '" . \IPS\Settings::i()->sql_database . "' AND TABLE_NAME='" . \IPS\Db::i()->prefix . "core_search_index'" ) )
|
||||
{
|
||||
if( $resultSet = $result->fetch_assoc() )
|
||||
{
|
||||
static::$tableCount = (int) $resultSet['_size'];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
|
||||
/* Did that fail? */
|
||||
if ( static::$tableCount === NULL )
|
||||
{
|
||||
/* Do it the old-fashioned way */
|
||||
static::$tableCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index' )->first();
|
||||
}
|
||||
|
||||
|
||||
return static::$tableCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,6 +283,16 @@ abstract class _Query
|
||||
{
|
||||
return 'relevancy';
|
||||
}
|
||||
|
||||
/**
|
||||
* Can use relevancy?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canUseRelevancy(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count
|
||||
|
||||
@@ -248,9 +248,15 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
|
||||
/* Load tags and prefixes */
|
||||
if ( \count( $itemIndexIds ) and \IPS\Settings::i()->search_method == 'mysql' )
|
||||
if ( \count( $itemIndexIds ) and \IPS\Settings::i()->search_method == 'mysql' and \IPS\Settings::i()->tags_enabled )
|
||||
{
|
||||
$this->tags = iterator_to_array( \IPS\Db::i()->select( 'index_id, GROUP_CONCAT(index_tag) as index_tags', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 0' ) ), NULL, NULL, 'index_id' )->setKeyField('index_id') );
|
||||
$orderBy = '';
|
||||
if ( \IPS\Settings::i()->tags_alphabetical )
|
||||
{
|
||||
$orderBy = ' ORDER BY index_tag ASC';
|
||||
}
|
||||
|
||||
$this->tags = iterator_to_array( \IPS\Db::i()->select( 'index_id, GROUP_CONCAT(index_tag' . $orderBy . ') as index_tags', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 0' ) ), NULL, NULL, 'index_id' )->setKeyField('index_id') );
|
||||
$this->prefixes = iterator_to_array( \IPS\Db::i()->select( 'index_id, index_tag as index_prefix', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 1' ) ) )->setKeyField('index_id') );
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Bluesky share link
|
||||
* @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 19 November 2024
|
||||
*/
|
||||
|
||||
namespace IPS\Content\ShareServices;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bluesky share link
|
||||
*/
|
||||
class _Bluesky extends \IPS\Content\ShareServices
|
||||
{
|
||||
/**
|
||||
* Return the HTML code to show the share link
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->bluesky( urlencode( $this->url ), $this->title ? urlencode( $this->title ) : NULL );
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,13 @@ abstract class _ShareServices
|
||||
public static function services()
|
||||
{
|
||||
return array(
|
||||
'Bluesky' => 'IPS\Content\ShareServices\Bluesky',
|
||||
'Facebook' => 'IPS\Content\ShareServices\Facebook',
|
||||
'Twitter' => 'IPS\Content\ShareServices\Twitter',
|
||||
'Linkedin' => 'IPS\Content\ShareServices\Linkedin',
|
||||
'Pinterest' => 'IPS\Content\ShareServices\Pinterest',
|
||||
'Reddit' => 'IPS\Content\ShareServices\Reddit',
|
||||
'Email' => 'IPS\Content\ShareServices\Email',
|
||||
'X' => 'IPS\Content\ShareServices\X',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Twitter share link
|
||||
* @brief X share link
|
||||
* @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 11 Sept 2013
|
||||
* @see <a href='https://dev.twitter.com/docs/tweet-button'>Tweet button documentation</a>
|
||||
* @since 26 Sept 2023
|
||||
* @see <a href='https://dev.x.com/docs/tweet-button'>X button documentation</a>
|
||||
*/
|
||||
|
||||
namespace IPS\Content\ShareServices;
|
||||
@@ -19,21 +19,17 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter share link
|
||||
* X share link
|
||||
*/
|
||||
class _Twitter extends \IPS\Content\ShareServices
|
||||
class _X extends \IPS\Content\ShareServices
|
||||
{
|
||||
/**
|
||||
* Determine whether the logged in user has the ability to autoshare
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function canAutoshare()
|
||||
public static function canAutoshare(): bool
|
||||
{
|
||||
if ( $method = \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\OAuth1\Twitter' ) and $method->canProcess( \IPS\Member::loggedIn() ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -46,25 +42,7 @@ class _Twitter extends \IPS\Content\ShareServices
|
||||
*/
|
||||
public static function publish( $content, $url=null )
|
||||
{
|
||||
if ( static::canAutoshare() and $method = \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\OAuth1\Twitter' ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( !$method->postToTwitter( \IPS\Member::loggedIn(), $content, $url ) )
|
||||
{
|
||||
throw new \Exception;
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( \IPS\Member::loggedIn()->member_id . ': '. $e->getMessage(), 'twitter' );
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_exception') );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_no_user') );
|
||||
}
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('x_publish_no_user') );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,17 +54,7 @@ class _Twitter extends \IPS\Content\ShareServices
|
||||
*/
|
||||
public static function modifyForm( \IPS\Helpers\Form &$form, $service )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'twitter_hashtag', \IPS\Settings::i()->twitter_hashtag, FALSE ) );
|
||||
|
||||
if ( \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\OAuth2\Twitter' ) )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Twitter', $service->autoshare, false ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Twitter', FALSE, false, array( 'disabled' => TRUE ) ) );
|
||||
\IPS\Member::loggedIn()->language()->words['share_autoshare_Twitter_desc'] = \IPS\Member::loggedIn()->language()->addToStack('share_autoshare_Twitter_disabled');
|
||||
}
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'x_hashtag', \IPS\Settings::i()->x_hashtag, FALSE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,11 +74,11 @@ class _Twitter extends \IPS\Content\ShareServices
|
||||
$this->url) ;
|
||||
|
||||
$title = $this->title ?: NULL;
|
||||
if ( \IPS\Settings::i()->twitter_hashtag !== '')
|
||||
if ( \IPS\Settings::i()->x_hashtag !== '')
|
||||
{
|
||||
$title .= ' ' . \IPS\Settings::i()->twitter_hashtag;
|
||||
$title .= ' ' . \IPS\Settings::i()->x_hashtag;
|
||||
}
|
||||
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->twitter( urlencode( $url ), rawurlencode( $title ) );
|
||||
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->x( urlencode( $url ), rawurlencode( $title ) );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
+106
-25
@@ -13,6 +13,8 @@ namespace IPS\Content;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use http\Exception\BadMethodCallException;
|
||||
use function array_merge;
|
||||
use function implode;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -50,7 +52,7 @@ trait Statistics
|
||||
*/
|
||||
public function imageAttachments( $count = 10 ): array
|
||||
{
|
||||
$attachments = $this->_getAllAttachments( array( 'attach_is_image=1' ), $count, 'attach_date DESC' );
|
||||
$attachments = $this->_getAllAttachments( array( 'attach_is_image=1' ), $count, 'attachment_id DESC' );
|
||||
$attachments = \array_slice( $attachments, 0, $count );
|
||||
|
||||
return $attachments;
|
||||
@@ -120,11 +122,12 @@ trait Statistics
|
||||
/**
|
||||
* Most Recent Participans
|
||||
*
|
||||
* @param string|NULL $name If we are looking for a specific user.
|
||||
* @param int $limit The amount of results to return.
|
||||
* @param string|NULL $name If we are looking for a specific user.
|
||||
* @param int $limit The amount of results to return.
|
||||
* @param bool $incBanned Include banned members? (Used to exclude from recent mentions)
|
||||
* @return \IPS\Patterns\ActiveRecordIterator
|
||||
*/
|
||||
public function mostRecent( ?string $name = NULL, int $limit = 10 )
|
||||
public function mostRecent( ?string $name = NULL, int $limit = 10, bool $incBanned = TRUE )
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
|
||||
@@ -135,11 +138,11 @@ trait Statistics
|
||||
|
||||
if ( $name )
|
||||
{
|
||||
$cacheKey = 'mostRecent_' . $limit . '_' . $name;
|
||||
$cacheKey = 'mostRecent_' . $limit . '_' . $name . '_' . (int) $incBanned;
|
||||
}
|
||||
else
|
||||
{
|
||||
$cacheKey = 'mostRecent_' . $limit;
|
||||
$cacheKey = 'mostRecent_' . $limit . '_' . (int) $incBanned;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -152,7 +155,13 @@ trait Statistics
|
||||
$where = $this->_getVisibleWhere();
|
||||
if ( $name )
|
||||
{
|
||||
$subQuery = \IPS\Db::i()->select( 'core_members.member_id', 'core_members', \IPS\Db::i()->like( 'core_members.name', $name ) );
|
||||
$subWhere = array();
|
||||
$subWhere[] = \IPS\Db::i()->like( 'core_members.name', $name );
|
||||
if ( !$incBanned )
|
||||
{
|
||||
$subWhere[] = array( "core_members.temp_ban=?", 0 );
|
||||
}
|
||||
$subQuery = \IPS\Db::i()->select( 'core_members.member_id', 'core_members', $subWhere );
|
||||
$where[] = [ $commentClass::$databaseTable . '.' . $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] . ' IN(?)', $subQuery ];
|
||||
}
|
||||
|
||||
@@ -299,13 +308,22 @@ trait Statistics
|
||||
/**
|
||||
* Clear any cached stats
|
||||
*
|
||||
* @param bool $force Force immediate cache delete
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function clearCachedStatistics()
|
||||
public function clearCachedStatistics( bool $force=FALSE )
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
\IPS\Db::i()->delete( 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ) );
|
||||
if( $force )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_item_statistics_cache', [ 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Set cache to time out in 10 minutes. */
|
||||
\IPS\Db::i()->update( 'core_item_statistics_cache', [ 'cache_added' => ( time() - 172200 ) ], [ 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,7 +352,13 @@ trait Statistics
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
$where = array( array( 'location_key=? and id1=? and id2 IN(?)', static::$application . '_' . mb_ucfirst( static::$module ), $this->$idField, $this->_subQueryVisibleComments( $this->$idField ) ) );
|
||||
/* This is more efficient and avoids a very slow table join */
|
||||
if ( \stristr( $orderBy, 'attach_date' ) )
|
||||
{
|
||||
$orderBy = str_replace( 'attach_date', 'attachment_id', $orderBy );
|
||||
}
|
||||
|
||||
$where = array( array( 'location_key=? and id1=?', static::$application . '_' . mb_ucfirst( static::$module ), $this->$idField ) );
|
||||
|
||||
if ( $extraWhere !== NULL )
|
||||
{
|
||||
@@ -342,7 +366,24 @@ trait Statistics
|
||||
}
|
||||
|
||||
/* Get attachments from all comments */
|
||||
$return = iterator_to_array( \IPS\Db::i()->select( '*', 'core_attachments_map', $where, $orderBy, $limit )->join( 'core_attachments', array( 'attach_id=attachment_id' ) ) );
|
||||
$commentClass = static::$commentClass;
|
||||
$commentTableJoin = [ 'id2=' . $commentClass::$databasePrefix . $commentClass::$databaseColumnId ];
|
||||
if ( isset( $commentClass::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$approvedColumn = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['approved'];
|
||||
$commentTableJoin[] = "{$approvedColumn} = 1";
|
||||
}
|
||||
if ( isset( $commentClass::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$hiddenColumn = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['hidden'];
|
||||
$commentTableJoin[] = "{$hiddenColumn} = 0";
|
||||
}
|
||||
|
||||
$return = iterator_to_array(
|
||||
\IPS\Db::i()->select( '*', 'core_attachments_map', $where, $orderBy, $limit )
|
||||
->join( 'core_attachments', array( 'attach_id=attachment_id' ) )
|
||||
->join( $commentClass::$databaseTable, array( implode( ' AND ', $commentTableJoin ) ), 'INNER' )
|
||||
);
|
||||
|
||||
/* Get link to comments */
|
||||
foreach( $return as $k => $map )
|
||||
@@ -443,6 +484,11 @@ trait Statistics
|
||||
*/
|
||||
static $cachedActivity = NULL;
|
||||
|
||||
/**
|
||||
* @var bool|null Process has rebuild lock
|
||||
*/
|
||||
protected ?bool $hasRebuildLock = NULL;
|
||||
|
||||
/**
|
||||
* Get the cached data
|
||||
*
|
||||
@@ -453,28 +499,57 @@ trait Statistics
|
||||
protected function _getCached( $key )
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
if( $this->hasRebuildLock === FALSE )
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$class = \get_class( $this );
|
||||
$arrayKey = $class.'.'.$this->$idField;
|
||||
if ( !isset( static::$cachedActivity[$arrayKey] ) )
|
||||
$arrayKey = $class . '.' . $this->$idField;
|
||||
if ( !isset( static::$cachedActivity[ $arrayKey ] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$cache = \IPS\Db::i()->select( '*', 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', $class, $this->$idField ) )->first();
|
||||
|
||||
if ( $cache['cache_added'] > time() - 86400 )
|
||||
$cache = \IPS\Db::i()->select( '*', 'core_item_statistics_cache', [ 'cache_class=? and cache_item_id=?', $class, $this->$idField ] )->first();
|
||||
if ( $cache['cache_added'] > ( time() - 172800 ) )
|
||||
{
|
||||
static::$cachedActivity[ $arrayKey ] = json_decode( $cache['cache_contents'], TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ) );
|
||||
$this->hasRebuildLock = TRUE;
|
||||
$affectedRows = \IPS\Db::i()->delete( 'core_item_statistics_cache', [ 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ] );
|
||||
if( $affectedRows == 0 )
|
||||
{
|
||||
/* We did not delete the row, allow another process to rebuild the cache */
|
||||
$this->hasRebuildLock = FALSE;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
/* Try to get the lock for new cache generation */
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_item_statistics_cache', [
|
||||
'cache_class' => \get_class( $this ),
|
||||
'cache_item_id' => $this->$idField,
|
||||
'cache_contents' => '[]',
|
||||
'cache_added' => time()
|
||||
] );
|
||||
$this->hasRebuildLock = TRUE;
|
||||
}
|
||||
/* We didn't get the lock, return empty array and allow other process to build cache */
|
||||
catch( \IPS\Db\Exception $e )
|
||||
{
|
||||
$this->hasRebuildLock = FALSE;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e ) { }
|
||||
}
|
||||
|
||||
if ( isset(static::$cachedActivity[ $arrayKey ] ) and isset( static::$cachedActivity[ $arrayKey ][ $key ] ) )
|
||||
if ( isset( static::$cachedActivity[ $arrayKey ] ) and isset( static::$cachedActivity[ $arrayKey ][ $key ] ) )
|
||||
{
|
||||
return static::$cachedActivity[ $arrayKey ][ $key ];
|
||||
}
|
||||
@@ -482,10 +557,10 @@ trait Statistics
|
||||
{
|
||||
if( !isset( static::$cachedActivity[ $arrayKey ] ) )
|
||||
{
|
||||
static::$cachedActivity[ $arrayKey ] = array();
|
||||
static::$cachedActivity[ $arrayKey ] = [];
|
||||
}
|
||||
|
||||
static::$cachedActivity[ $arrayKey ][ $key ] = array();
|
||||
static::$cachedActivity[ $arrayKey ][ $key ] = [];
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
@@ -528,14 +603,20 @@ trait Statistics
|
||||
{
|
||||
if( $this->storeCache )
|
||||
{
|
||||
$insert = [];
|
||||
foreach( $this->storeCache as $key => $data )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_item_statistics_cache', array(
|
||||
$insert[] = array(
|
||||
'cache_class' => \get_class( $this ),
|
||||
'cache_item_id' => str_replace( \get_class($this) . '.', '', $key),
|
||||
'cache_contents' => json_encode( static::$cachedActivity[$key] ),
|
||||
'cache_item_id' => str_replace( \get_class( $this ) . '.', '', $key ),
|
||||
'cache_contents' => json_encode( static::$cachedActivity[ $key ] ) ?? '[]',
|
||||
'cache_added' => time()
|
||||
), TRUE );
|
||||
);
|
||||
}
|
||||
|
||||
if( \count( $insert ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_item_statistics_cache', $insert, TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
|
||||
) );
|
||||
|
||||
/* Tags */
|
||||
if( \IPS\Settings::i()->tags_enabled )
|
||||
if( \in_array( 'IPS\Content\Tags', class_implements( $class ) ) and \IPS\Settings::i()->tags_enabled )
|
||||
{
|
||||
if ( \IPS\Settings::i()->tags_force_lower )
|
||||
{
|
||||
@@ -254,7 +254,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
|
||||
{
|
||||
$class = static::$class;
|
||||
|
||||
if ( \is_array( $values[ 'widget_feed_container_' . $class::$title ] ) )
|
||||
if ( isset( $class::$containerNodeClass ) and \is_array( $values[ 'widget_feed_container_' . $class::$title ] ) )
|
||||
{
|
||||
$values['widget_feed_container'] = array_keys( $values[ 'widget_feed_container_' . $class::$title ] );
|
||||
unset( $values[ 'widget_feed_container_' . $class::$title ] );
|
||||
|
||||
Reference in new issue
Block a user