Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
+335
-142
@@ -12,13 +12,37 @@ namespace IPS\Content;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use http\Exception\BadMethodCallException;
|
||||
use Exception;
|
||||
use IPS\Application;
|
||||
use IPS\DateTime as IPSDateTime; // DateTime aliased to avoid conflicts with base DateTime class to ensure \IPS\DateTime is actually being used.
|
||||
use IPS\Db;
|
||||
use IPS\Db\Select;
|
||||
use IPS\IPS;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use LogicException;
|
||||
use OutOfRangeException;
|
||||
use UnderflowException;
|
||||
use UnexpectedValueException;
|
||||
use BadMethodCallException;
|
||||
use function array_push;
|
||||
use function array_slice;
|
||||
use function defined;
|
||||
use function explode;
|
||||
use function get_class;
|
||||
use function in_array;
|
||||
use function is_callable;
|
||||
use function iterator_to_array;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function md5;
|
||||
use function stristr;
|
||||
use function time;
|
||||
use function array_merge;
|
||||
use function implode;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -27,20 +51,98 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
trait Statistics
|
||||
{
|
||||
|
||||
/**
|
||||
* Get stats for many items at once. Trying caches first to minimise queries
|
||||
*
|
||||
* @param string $type
|
||||
* @param array $items
|
||||
* @return array
|
||||
*/
|
||||
public static function getMany( string $type, array $items ): array
|
||||
{
|
||||
$itemIds = [];
|
||||
$class = null;
|
||||
$return = [];
|
||||
|
||||
if ( ! count( $items ) )
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach( $items as $item )
|
||||
{
|
||||
/* This is genuis and you can't tell me otherwise */
|
||||
if ( $class === null )
|
||||
{
|
||||
$class = get_class( $item );
|
||||
}
|
||||
|
||||
/* @var Item $class */
|
||||
$idColumn = $class::$databaseColumnId;
|
||||
$itemIds[ $item->$idColumn ] = $item;
|
||||
}
|
||||
|
||||
foreach( Db::i()->select( '*', 'core_item_statistics_cache', [ [ 'cache_class=?', $class ], [ Db::i()->in( 'cache_item_id', array_keys( $itemIds ) ) ] ] ) as $cache )
|
||||
{
|
||||
if ( $cache['cache_added'] > time() - 86400 )
|
||||
{
|
||||
$json = json_decode( $cache['cache_contents'], TRUE );
|
||||
|
||||
if ( isset( $json[ $type ] ) )
|
||||
{
|
||||
$return[ $cache['cache_item_id'] ] = $json[ $type ];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We may have multiple types such as topPosters_4 or topPosters_10, so we make a guess here if the type sent is just 'topPosters' */
|
||||
foreach( $json as $key => $data )
|
||||
{
|
||||
if ( strpos( $key, $type ) === 0 )
|
||||
{
|
||||
$return[ $cache['cache_item_id'] ] = $data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$diff = array_diff( array_keys( $itemIds ), array_keys( $return ) );
|
||||
|
||||
if ( count( $diff ) )
|
||||
{
|
||||
foreach( $diff as $id )
|
||||
{
|
||||
/* Make sure the method is 'topPosters' and not 'topPosters_10' */
|
||||
$method = preg_replace( '#([a-zA-Z0-9]*)(_*|$)#', '$1', $type );
|
||||
if ( method_exists( $class, $method ) )
|
||||
{
|
||||
$return[ $id ] = $itemIds[ $id ]->$type();
|
||||
}
|
||||
else
|
||||
{
|
||||
$return[ $id ] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Most downloaded attachments
|
||||
*
|
||||
* @param int $count The number of results to return
|
||||
* @return array
|
||||
* @throw BadMethodCallException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function topAttachments( $count = 5 ): array
|
||||
public function topAttachments( int $count = 5 ): array
|
||||
{
|
||||
$attachments = $this->_getAllAttachments( array(), $count, 'attach_hits DESC' );
|
||||
$attachments = $this->_getAllAttachments( array(), $count, 'attach_hits DESC', 'topAttachments' );
|
||||
|
||||
$attachments = \array_slice( $attachments, 0, $count );
|
||||
|
||||
return $attachments;
|
||||
return array_slice( $attachments, 0, $count );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,13 +151,12 @@ trait Statistics
|
||||
* @param int $count The number of results to return
|
||||
* @return array
|
||||
* @throw BadMethodCallException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function imageAttachments( $count = 10 ): array
|
||||
public function imageAttachments( int $count = 10 ): array
|
||||
{
|
||||
$attachments = $this->_getAllAttachments( array( 'attach_is_image=1' ), $count, 'attachment_id DESC' );
|
||||
$attachments = \array_slice( $attachments, 0, $count );
|
||||
|
||||
return $attachments;
|
||||
$attachments = $this->_getAllAttachments( array( 'attach_is_image=1' ), $count, 'attachment_id DESC', 'imageAttachments' );
|
||||
return array_slice( $attachments, 0, $count );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,16 +164,16 @@ trait Statistics
|
||||
*
|
||||
* @param int $count The number of results to return
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @throw BadMethodCallException
|
||||
*/
|
||||
public function topPosters( $count = 10 ): array
|
||||
public function topPosters( int $count = 10 ): array
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
|
||||
if ( !isset( $commentClass::$databaseColumnMap['author'] ) )
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
$authorColumn = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'];
|
||||
@@ -82,12 +183,12 @@ trait Statistics
|
||||
{
|
||||
$members = $this->_getCached( $cacheKey );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
$where = $this->_getVisibleWhere();
|
||||
$where[] = [ $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] . '!=?', 0];
|
||||
|
||||
$members = iterator_to_array( \IPS\Db::i()->select( "count(*) as sum, {$authorColumn}", $commentClass::$databaseTable, $where, 'sum DESC', array( 0, $count ), array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] ) ) );
|
||||
$members = iterator_to_array( Db::i()->select( "count(*) as sum, {$authorColumn}", $commentClass::$databaseTable, $where, 'sum DESC', array( 0, $count ), array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] ) ) );
|
||||
|
||||
$this->_storeCached( $cacheKey, $members );
|
||||
}
|
||||
@@ -106,7 +207,7 @@ trait Statistics
|
||||
}
|
||||
|
||||
$return = array();
|
||||
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', $contributors ) ) ), 'IPS\Member' ) as $member )
|
||||
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'core_members', array( Db::i()->in( 'member_id', $contributors ) ) ), 'IPS\Member' ) as $member )
|
||||
{
|
||||
$return[] = array( 'member' => $member, 'count' => $counts[ $member->member_id ] );
|
||||
}
|
||||
@@ -125,15 +226,15 @@ trait Statistics
|
||||
* @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
|
||||
* @return ActiveRecordIterator
|
||||
*/
|
||||
public function mostRecent( ?string $name = NULL, int $limit = 10, bool $incBanned = TRUE )
|
||||
public function mostRecent( string|null $name = NULL, int $limit = 10, bool $incBanned = true ): ActiveRecordIterator
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
|
||||
if ( !isset( $commentClass::$databaseColumnMap['author'] ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
throw new BadMethodCallException;
|
||||
}
|
||||
|
||||
if ( $name )
|
||||
@@ -149,23 +250,23 @@ trait Statistics
|
||||
{
|
||||
$members = $this->_getCached( $cacheKey );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
/* Get the ten most recent posters in this content that match input. */
|
||||
$where = $this->_getVisibleWhere();
|
||||
if ( $name )
|
||||
{
|
||||
$subWhere = array();
|
||||
$subWhere[] = \IPS\Db::i()->like( 'core_members.name', $name );
|
||||
$subWhere[] = 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 );
|
||||
$subQuery = Db::i()->select( 'core_members.member_id', 'core_members', $subWhere );
|
||||
$where[] = [ $commentClass::$databaseTable . '.' . $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] . ' IN(?)', $subQuery ];
|
||||
}
|
||||
|
||||
$members = iterator_to_array( \IPS\Db::i()->select(
|
||||
$members = iterator_to_array( Db::i()->select(
|
||||
$commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'],
|
||||
$commentClass::$databaseTable,
|
||||
$where,
|
||||
@@ -175,7 +276,54 @@ trait Statistics
|
||||
$this->_storeCached( $cacheKey, $members );
|
||||
}
|
||||
|
||||
return new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', $members ) ) ), 'IPS\Member' );
|
||||
return new ActiveRecordIterator( Db::i()->select( '*', 'core_members', array( Db::i()->in( 'member_id', $members ) ) ), 'IPS\Member' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reactions and the count of the times used
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function allReactions(): array
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
$cacheKey = 'allReactions';
|
||||
$return = [];
|
||||
$enabledReactions = Reaction::enabledReactions();
|
||||
|
||||
if ( ! $enabledReactions )
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$return = $this->_getCached( $cacheKey );
|
||||
}
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
/* Get reactions from all comments. Using distinct or group by is instant death by a thousand seconds (of execution time). Max of 100k to prevent memory exhaustion */
|
||||
$reactions = iterator_to_array( Db::i()->select( 'reaction', 'core_reputation_index', [ [ 'rep_class=? and type_id IN(?)', static::$commentClass, $this->_subQueryVisibleComments( $this->$idField ) ] ], null, 100000 ) );
|
||||
|
||||
/* Get link to comments */
|
||||
foreach( $reactions as $reaction )
|
||||
{
|
||||
if( isset( $enabledReactions[ $reaction ] ) )
|
||||
{
|
||||
if ( !isset( $return[ $reaction ] ) )
|
||||
{
|
||||
$return[ $reaction ] = 0;
|
||||
}
|
||||
|
||||
$return[ $reaction ]++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->_storeCached( $cacheKey, $return );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,22 +332,22 @@ trait Statistics
|
||||
* @param int $count The number of results to return, max 100
|
||||
* @return array
|
||||
* @throw BadMethodCallException
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function topReactedPosts( $count = 5 ): array
|
||||
public function topReactedPosts( int $count = 5 ): array
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
$commentIdField = $commentClass::$databasePrefix . $commentClass::$databaseColumnId;
|
||||
$idField = static::$databaseColumnId;
|
||||
|
||||
if ( !\IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Reactable' ) )
|
||||
if ( !IPS::classUsesTrait( $commentClass, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
if ( $count > 100 )
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
$cacheKey = 'topReactedPosts_' . $count;
|
||||
@@ -208,10 +356,21 @@ trait Statistics
|
||||
{
|
||||
$posts = $this->_getCached( $cacheKey );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
$where = array( 'app=? and type=? and item_id=?', $commentClass::$application, $commentClass::reactionType(), $this->$idField );
|
||||
$posts = \IPS\Db::i()->select( "count(*) as sum, type_id", 'core_reputation_index', $where, NULL, NULL, array( 'type_id' ) );
|
||||
$where = [
|
||||
[ 'app=?', $commentClass::$application ],
|
||||
[ 'type=?', $commentClass::reactionType() ],
|
||||
[ 'item_id=?', $this->$idField ]
|
||||
];
|
||||
|
||||
/* Exclude the first post */
|
||||
if( $firstComment = $this->mapped( 'first_comment_id' ) )
|
||||
{
|
||||
$where[] = [ 'type_id <> ?', $firstComment ];
|
||||
}
|
||||
|
||||
$posts = Db::i()->select( "count(*) as sum, type_id", 'core_reputation_index', $where, NULL, NULL, array( 'type_id' ) );
|
||||
|
||||
$posts = iterator_to_array( $posts );
|
||||
|
||||
@@ -221,7 +380,7 @@ trait Statistics
|
||||
} );
|
||||
|
||||
/* Just store the top 100 posts, they are already sorted by highest to lowest */
|
||||
$posts = \array_slice( $posts, 0, 100 );
|
||||
$posts = array_slice( $posts, 0, 100 );
|
||||
$this->_storeCached( $cacheKey, $posts );
|
||||
}
|
||||
|
||||
@@ -239,7 +398,79 @@ trait Statistics
|
||||
}
|
||||
|
||||
$return = array();
|
||||
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', $commentClass::$databaseTable, array( \IPS\Db::i()->in( $commentIdField, $postIds ) ), "FIND_IN_SET( {$commentIdField}, '" . implode( ",", $postIds ) . "' )", array( 0, $count ) ), $commentClass ) as $comment )
|
||||
foreach( new ActiveRecordIterator( Db::i()->select( '*', $commentClass::$databaseTable, array( Db::i()->in( $commentIdField, $postIds ) ), "FIND_IN_SET( {$commentIdField}, '" . implode( ",", $postIds ) . "' )", array( 0, $count ) ), $commentClass ) as $comment )
|
||||
{
|
||||
if ( $comment->canView() and $comment->mapped('item') == $this->$idField and isset( $counts[ $comment->$commentIdField ] ) )
|
||||
{
|
||||
$return[] = array( 'comment' => $comment, 'count' => $counts[ $comment->$commentIdField ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Most helpful posts
|
||||
*
|
||||
* @param int $count The number of results to return, max 100
|
||||
* @return array
|
||||
* @throw BadMethodCallException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function helpfulPosts( int $count = 5 ): array
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
$commentIdField = $commentClass::$databasePrefix . $commentClass::$databaseColumnId;
|
||||
$idField = static::$databaseColumnId;
|
||||
|
||||
if ( !IPS::classUsesTrait( $commentClass, 'IPS\Content\Helpful' ) )
|
||||
{
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
if ( $count > 100 )
|
||||
{
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
$cacheKey = 'helpfulPosts_' . $count;
|
||||
|
||||
try
|
||||
{
|
||||
$posts = $this->_getCached( $cacheKey );
|
||||
}
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
$where = array( 'app=? and type=? and item_id=? and hidden=0', $commentClass::$application, 'helpful', $this->$idField );
|
||||
$posts = Db::i()->select( "count(*) as sum, comment_id", 'core_solved_index', $where, NULL, NULL, array( 'comment_id' ) );
|
||||
|
||||
$posts = iterator_to_array( $posts );
|
||||
|
||||
usort( $posts, function( $item1, $item2 )
|
||||
{
|
||||
return $item2['sum'] <=> $item1['sum'];
|
||||
} );
|
||||
|
||||
/* Just store the top 100 posts, they are already sorted by highest to lowest */
|
||||
$posts = array_slice( $posts, 0, 100 );
|
||||
$this->_storeCached( $cacheKey, $posts );
|
||||
}
|
||||
|
||||
$postIds = array();
|
||||
$counts = array();
|
||||
foreach ( $posts as $post )
|
||||
{
|
||||
$postIds[] = $post['comment_id'];
|
||||
$counts[$post['comment_id']] = $post['sum'];
|
||||
}
|
||||
|
||||
if ( empty( $postIds ) )
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$return = array();
|
||||
foreach( new ActiveRecordIterator( Db::i()->select( '*', $commentClass::$databaseTable, array( Db::i()->in( $commentIdField, $postIds ) ), "FIND_IN_SET( {$commentIdField}, '" . implode( ",", $postIds ) . "' )", array( 0, $count ) ), $commentClass ) as $comment )
|
||||
{
|
||||
if ( $comment->canView() and $comment->mapped('item') == $this->$idField and isset( $counts[ $comment->$commentIdField ] ) )
|
||||
{
|
||||
@@ -255,9 +486,9 @@ trait Statistics
|
||||
*
|
||||
* @param int $count Number of days to return
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function popularDays( $count=10 )
|
||||
public function popularDays( int $count=10 ): array
|
||||
{
|
||||
$return = array();
|
||||
$commentClass = static::$commentClass;
|
||||
@@ -271,19 +502,20 @@ trait Statistics
|
||||
{
|
||||
$posts = $this->_getCached( $cacheKey );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
$dateColumn = $commentClass::$databasePrefix . ( isset( $commentClass::$databaseColumnMap['updated'] ) ? $commentClass::$databaseColumnMap['updated'] : $commentClass::$databaseColumnMap['date'] );
|
||||
/* @var $databaseColumnMap array */
|
||||
$dateColumn = $commentClass::$databasePrefix . ( $commentClass::$databaseColumnMap['updated'] ?? $commentClass::$databaseColumnMap['date'] );
|
||||
$where = $this->_getVisibleWhere();
|
||||
|
||||
$posts = iterator_to_array( \IPS\Db::i()->select( "COUNT(*) AS count, MIN({$commentIdField}) as commentId, (DATE_FORMAT( FROM_UNIXTIME( IFNULL( {$dateColumn}, 0 ) ), '%Y-%c-%e' ) ) as time", $commentClass::$databaseTable, $where, 'count desc', array( 0, $count ), array( 'time' ) ) );
|
||||
$posts = iterator_to_array( Db::i()->select( "COUNT(*) AS count, MIN({$commentIdField}) as commentId, (DATE_FORMAT( FROM_UNIXTIME( IFNULL( {$dateColumn}, 0 ) ), '%Y-%c-%e' ) ) as time", $commentClass::$databaseTable, $where, 'count desc', array( 0, $count ), array( 'time' ) ) );
|
||||
|
||||
$this->_storeCached( $cacheKey, $posts );
|
||||
}
|
||||
|
||||
foreach ( $posts as $row )
|
||||
foreach ( $posts as $row )
|
||||
{
|
||||
if ( ! \in_array( $row['time'], $rows ) )
|
||||
if ( ! in_array( $row['time'], $rows ) )
|
||||
{
|
||||
$rows[ $row['time'] ] = 0;
|
||||
}
|
||||
@@ -294,7 +526,7 @@ trait Statistics
|
||||
|
||||
foreach ( $rows as $time => $val )
|
||||
{
|
||||
$datetime = new \IPS\DateTime;
|
||||
$datetime = new IPSDateTime;
|
||||
$datetime->setTime( 12, 0, 0 );
|
||||
$exploded = explode( '-', $time );
|
||||
$datetime->setDate( $exploded[0], $exploded[1], $exploded[2] );
|
||||
@@ -308,22 +540,13 @@ trait Statistics
|
||||
/**
|
||||
* Clear any cached stats
|
||||
*
|
||||
* @param bool $force Force immediate cache delete
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function clearCachedStatistics( bool $force=FALSE )
|
||||
public function clearCachedStatistics(): void
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
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 ] );
|
||||
}
|
||||
Db::i()->delete( 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', get_class( $this ), $this->$idField ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,31 +557,32 @@ trait Statistics
|
||||
/**
|
||||
* Get all attachments for this item
|
||||
*
|
||||
* @param array $extraWhere Additional where clause
|
||||
* @param int $limit Number to return/limit to
|
||||
* @param string|NULL $orderBy Order by clause (optional)
|
||||
* @param array|null $extraWhere Additional where clause
|
||||
* @param int $limit Number to return/limit to
|
||||
* @param string|NULL $orderBy Order by clause (optional)
|
||||
* @param string $cacheName
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _getAllAttachments( $extraWhere=NULL, $limit=10, $orderBy=NULL )
|
||||
protected function _getAllAttachments( array|null $extraWhere=NULL, int $limit=10, string|null $orderBy=NULL, $cacheName='allAttachments' ): array
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
$cacheKey = 'allAttachments' . md5( json_encode( $extraWhere ) . $limit . (string) $orderBy );
|
||||
$cacheKey = $cacheName . '_' . md5( json_encode( $extraWhere ) . $limit . $orderBy );
|
||||
$return = array();
|
||||
|
||||
try
|
||||
{
|
||||
$return = $this->_getCached( $cacheKey );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
/* This is more efficient and avoids a very slow table join */
|
||||
if ( \stristr( $orderBy, 'attach_date' ) )
|
||||
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 ) );
|
||||
$where = array( array( 'location_key=? and id1=?', static::$application . '_' . IPS::mb_ucfirst( static::$module ), $this->$idField ) );
|
||||
|
||||
if ( $extraWhere !== NULL )
|
||||
{
|
||||
@@ -380,7 +604,7 @@ trait Statistics
|
||||
}
|
||||
|
||||
$return = iterator_to_array(
|
||||
\IPS\Db::i()->select( '*', 'core_attachments_map', $where, $orderBy, $limit )
|
||||
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' )
|
||||
);
|
||||
@@ -394,14 +618,13 @@ trait Statistics
|
||||
$exploded = explode( '_', $map['location_key'] );
|
||||
try
|
||||
{
|
||||
$extensions = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'EditorLocations' );
|
||||
$extensions = Application::load( $exploded[0] )->extensions( 'core', 'EditorLocations' );
|
||||
if ( isset( $extensions[ $exploded[1] ] ) )
|
||||
{
|
||||
static::$loadedExtensions[ $map['location_key'] ] = $extensions[ $exploded[1] ];
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
catch ( \UnexpectedValueException $e ) { }
|
||||
catch ( OutOfRangeException | UnexpectedValueException $e ) { }
|
||||
}
|
||||
|
||||
if ( isset( static::$loadedExtensions[ $map['location_key'] ] ) )
|
||||
@@ -412,8 +635,7 @@ trait Statistics
|
||||
|
||||
$return[ $k ]['commentUrl'] = (string) $url->url();
|
||||
}
|
||||
catch ( \LogicException $e ) { }
|
||||
catch ( \BadMethodCallException $e ){ }
|
||||
catch ( LogicException|BadMethodCallException $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,12 +649,13 @@ trait Statistics
|
||||
* Return a sub query to fetch only visible posts
|
||||
*
|
||||
* @param int $id Content item ID
|
||||
* @return \IPS\Db\Select
|
||||
* @return Select
|
||||
*/
|
||||
protected function _subQueryVisibleComments( $id )
|
||||
protected function _subQueryVisibleComments( int $id ): Select
|
||||
{
|
||||
$commentClass = static::$commentClass;
|
||||
return \IPS\Db::i()->select( $commentClass::$databasePrefix . $commentClass::$databaseColumnId, $commentClass::$databaseTable, array_merge( $this->_getVisibleWhere(), array( array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=' . $id ) ) ) );
|
||||
/* @var $databaseColumnMap array */
|
||||
return Db::i()->select( $commentClass::$databasePrefix . $commentClass::$databaseColumnId, $commentClass::$databaseTable, array_merge( $this->_getVisibleWhere(), array( array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=' . $id ) ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,8 +663,9 @@ trait Statistics
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _getVisibleWhere()
|
||||
protected function _getVisibleWhere(): array
|
||||
{
|
||||
/* @var $databaseColumnMap array */
|
||||
$commentClass = static::$commentClass;
|
||||
|
||||
$lookFor = 'IPS\cms\Records\CommentTopicSync';
|
||||
@@ -469,6 +693,13 @@ trait Statistics
|
||||
$where[] = array( "{$hiddenColumn} = 0" );
|
||||
}
|
||||
|
||||
/* Exclude first posts */
|
||||
if ( isset( $commentClass::$databaseColumnMap['first'] ) )
|
||||
{
|
||||
$firstCommentColumn = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['first'];
|
||||
$where[] = array( "{$firstCommentColumn} = 0" );
|
||||
}
|
||||
|
||||
if ( $commentClass::commentWhere() !== NULL )
|
||||
{
|
||||
$where[] = $commentClass::commentWhere();
|
||||
@@ -482,74 +713,40 @@ trait Statistics
|
||||
/**
|
||||
* @brief Cached data
|
||||
*/
|
||||
static $cachedActivity = NULL;
|
||||
|
||||
/**
|
||||
* @var bool|null Process has rebuild lock
|
||||
*/
|
||||
protected ?bool $hasRebuildLock = NULL;
|
||||
public static array|null $cachedActivity = NULL;
|
||||
|
||||
/**
|
||||
* Get the cached data
|
||||
*
|
||||
* @param string $key Key to get
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
* @return mixed|NULL
|
||||
*/
|
||||
protected function _getCached( $key )
|
||||
protected function _getCached( string $key ): mixed
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
if( $this->hasRebuildLock === FALSE )
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$class = \get_class( $this );
|
||||
$arrayKey = $class . '.' . $this->$idField;
|
||||
if ( !isset( static::$cachedActivity[ $arrayKey ] ) )
|
||||
$class = get_class( $this );
|
||||
$arrayKey = $class.'.'.$this->$idField;
|
||||
if ( !isset( static::$cachedActivity[$arrayKey] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$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 ) )
|
||||
$cache = Db::i()->select( '*', 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', $class, $this->$idField ) )->first();
|
||||
|
||||
if ( $cache['cache_added'] > time() - 86400 )
|
||||
{
|
||||
static::$cachedActivity[ $arrayKey ] = json_decode( $cache['cache_contents'], TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
$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 [];
|
||||
Db::i()->delete( 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', get_class( $this ), $this->$idField ) );
|
||||
}
|
||||
}
|
||||
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 ];
|
||||
}
|
||||
@@ -557,36 +754,38 @@ trait Statistics
|
||||
{
|
||||
if( !isset( static::$cachedActivity[ $arrayKey ] ) )
|
||||
{
|
||||
static::$cachedActivity[ $arrayKey ] = [];
|
||||
static::$cachedActivity[ $arrayKey ] = array();
|
||||
}
|
||||
|
||||
static::$cachedActivity[ $arrayKey ][ $key ] = [];
|
||||
throw new \OutOfRangeException;
|
||||
static::$cachedActivity[ $arrayKey ][ $key ] = array();
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Should we store the data in the cache?
|
||||
*/
|
||||
protected $storeCache = NULL;
|
||||
protected array|null $storeCache = NULL;
|
||||
|
||||
/**
|
||||
* Set cached data
|
||||
*
|
||||
* @param string $key Key to store
|
||||
* @param mixed $value Value to store
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _storeCached( $key, $value )
|
||||
protected function _storeCached( string $key, mixed $value ): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->_getCached( $key );
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
catch( Exception $e ) { }
|
||||
|
||||
$idField = static::$databaseColumnId;
|
||||
$class = \get_class( $this );
|
||||
$class = get_class( $this );
|
||||
$arrayKey = $class.'.'.$this->$idField;
|
||||
|
||||
static::$cachedActivity[ $arrayKey ][ $key ] = $value;
|
||||
@@ -603,24 +802,18 @@ trait Statistics
|
||||
{
|
||||
if( $this->storeCache )
|
||||
{
|
||||
$insert = [];
|
||||
foreach( $this->storeCache as $key => $data )
|
||||
{
|
||||
$insert[] = array(
|
||||
'cache_class' => \get_class( $this ),
|
||||
'cache_item_id' => str_replace( \get_class( $this ) . '.', '', $key ),
|
||||
'cache_contents' => json_encode( static::$cachedActivity[ $key ] ) ?? '[]',
|
||||
Db::i()->insert( 'core_item_statistics_cache', array(
|
||||
'cache_class' => get_class( $this ),
|
||||
'cache_item_id' => str_replace( get_class($this) . '.', '', $key),
|
||||
'cache_contents' => json_encode( static::$cachedActivity[$key] ),
|
||||
'cache_added' => time()
|
||||
);
|
||||
}
|
||||
|
||||
if( \count( $insert ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_item_statistics_cache', $insert, TRUE );
|
||||
), TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
if( \is_callable( 'parent::__destruct' ) )
|
||||
if( is_callable( 'parent::__destruct' ) )
|
||||
{
|
||||
parent::__destruct();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user