Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+137 -76
View File
@@ -32,6 +32,16 @@ trait Reactable
throw new \BadMethodCallException;
}
/**
* Reaction class
*
* @return string
*/
public static function reactionClass()
{
return get_called_class();
}
/**
* React
*
@@ -54,9 +64,9 @@ trait Reactable
{
$owner = $this->owner();
}
/* Can we react? */
if ( $this->canReact( $member ) === FALSE )
if ( !$this->canView( $member ) or !$this->canReact( $member ) or !$reaction->enabled )
{
throw new \DomainException( 'cannot_react' );
}
@@ -92,7 +102,10 @@ trait Reactable
}
/* Have we already reacted? */
if ( $this->reacted( $member ) )
$reacted = $this->reacted( $member );
/* Remove the initial reaction, if we have reacted */
if ( $reacted )
{
$this->removeReaction( $member );
}
@@ -107,16 +120,16 @@ trait Reactable
'rep_date' => \IPS\DateTime::create()->getTimestamp(),
'rep_rating' => $reaction->value,
'member_received' => $owner->member_id,
'rep_class' => get_class( $this ),
'class_type_id_hash' => md5( get_class( $this ) . ':' . $this->$idColumn ),
'rep_class' => static::reactionClass(),
'class_type_id_hash' => md5( static::reactionClass() . ':' . $this->$idColumn ),
'item_id' => $itemId,
'reaction' => $reaction->id
) );
/* Send the notification */
if ( $this->author()->member_id AND $this->author() != \IPS\Member::loggedIn() AND $this->canView( $owner ) )
/* Send the notification but only if we aren't reacting to our own content, we can view the content, the user isn't ignored and we aren't changing from one reaction to another */
if ( $this->author()->member_id AND $this->author() != \IPS\Member::loggedIn() AND $this->canView( $owner ) AND !$reacted AND !$member->isIgnoring( $this->author(), 'posts' ) )
{
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'new_likes', $this, array( $this, $member ) );
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'new_likes', $this, array( $this, $member ), array(), TRUE, \IPS\Content\Reaction::isLikeMode() ? NULL : 'notification_new_react' );
$notification->recipients->attach( $owner );
$notification->send();
}
@@ -126,6 +139,12 @@ trait Reactable
$owner->pp_reputation_points += $reaction->value;
$owner->save();
}
/* Reset some cached values */
$this->_reactionCount = NULL;
$this->_reactions = NULL;
$this->hasReacted[ $member->member_id ] = $reaction;
}
/**
@@ -143,7 +162,10 @@ trait Reactable
try
{
$idColumn = static::$databaseColumnId;
$rep = \IPS\Db::i()->select( '*', 'core_reputation_index', array( "member_id=? AND rep_class=? AND type=? AND type_id=?", $member->member_id, get_class( $this ), static::reactionType(), $this->$idColumn ) )->first();
$where = $this->getReactionWhereClause( NULL, FALSE );
$where[] = array( 'member_id=?', $member->member_id );
$rep = \IPS\Db::i()->select( '*', 'core_reputation_index', $where )->first();
}
catch( \UnderflowException $e )
{
@@ -165,6 +187,30 @@ trait Reactable
}
\IPS\Db::i()->delete( 'core_reputation_index', array( "id=?", $rep['id'] ) );
/* Remove Notifications */
$memberIds = array();
foreach( \IPS\Db::i()->select( 'member', 'core_notifications', array( 'notification_key=? AND item_class=? AND item_id=?', 'new_likes', (string) get_class( $this ), (int) $this->$idColumn ) ) as $member )
{
$memberIds[ $member ] = $member;
}
\IPS\Db::i()->delete( 'core_notifications', array( 'notification_key=? AND item_class=? AND item_id=?', 'new_likes', (string) get_class( $this ), (int) $this->$idColumn ) );
foreach( $memberIds as $member )
{
\IPS\Member::load( $member )->recountNotifications();
}
/* Reset some cached values */
$this->_reactionCount = NULL;
$this->_reactions = NULL;
if( isset( $this->hasReacted[ $member->member_id ] ) )
{
unset( $this->hasReacted[ $member->member_id ] );
}
}
/**
@@ -244,20 +290,25 @@ trait Reactable
if ( is_array( $this->reputation ) )
{
foreach( $this->reputation AS $memberId => $reactionId )
if ( $enabledReactions = \IPS\Content\Reaction::enabledReactions() )
{
try
foreach( $this->reputation AS $memberId => $reactionId )
{
$this->_reactionCount += \IPS\Content\Reaction::load( $reactionId )->value;
$this->_reactions[ $memberId ][] = $reactionId;
if( isset( $enabledReactions[ $reactionId ] ) )
{
$this->_reactionCount += $enabledReactions[ $reactionId ]->value;
$this->_reactions[ $memberId ][] = $reactionId;
}
}
catch( \OutOfRangeException $e ) { }
}
}
else
{
/* Set the data in $this->reputation to save queries later */
$this->reputation = array();
foreach( \IPS\Db::i()->select( '*', 'core_reputation_index', $this->getReactionWhereClause() )->join( 'core_reactions', 'reaction=reaction_id' ) AS $reaction )
{
$this->reputation[ $reaction['member_id'] ] = $reaction['reaction'];
$this->_reactions[ $reaction['member_id'] ][] = $reaction['reaction'];
$this->_reactionCount += $reaction['rep_rating'];
}
@@ -279,20 +330,30 @@ trait Reactable
*/
public function reactionCount()
{
$this->reactions();
if( $this->_reactionCount === NULL )
{
$this->reactions();
}
return $this->_reactionCount;
}
/**
* Reaction Where Clause
*
* @param \IPS\Content\Reaction|array|int|NULL This can be any one of the following: An \IPS\Content\Reaction object, an array of \IPS\Content\Reaction objects, and integer, or an array of integers, or NULL
* @param \IPS\Content\Reaction|array|int|NULL $reactions This can be any one of the following: An \IPS\Content\Reaction object, an array of \IPS\Content\Reaction objects, and integer, or an array of integers, or NULL
* @param bool $enabledTypesOnly If TRUE, only reactions of the enabled reaction types will be included (must join core_reactions)
* @return array
*/
public function getReactionWhereClause( $reactions = NULL )
public function getReactionWhereClause( $reactions = NULL, $enabledTypesOnly=TRUE )
{
$idColumn = static::$databaseColumnId;
$where = array( array( 'rep_class=? AND type=? AND type_id=? AND reaction_enabled=?', get_class( $this ), static::reactionType(), $this->$idColumn, 1 ) );
$where = array( array( 'rep_class=? AND type=? AND type_id=?', static::reactionClass(), static::reactionType(), $this->$idColumn ) );
if ( $enabledTypesOnly )
{
$where[] = array( 'reaction_enabled=1' );
}
if ( $reactions !== NULL )
{
@@ -330,7 +391,7 @@ trait Reactable
*/
public function reactionTable( $reaction=NULL )
{
if ( !\IPS\Member::loggedIn()->group['gbw_view_reps'] )
if ( !\IPS\Member::loggedIn()->group['gbw_view_reps'] or !$this->canView() )
{
throw new \DomainException;
}
@@ -358,7 +419,12 @@ trait Reactable
return $table;
}
/**
* @brief Cached Reacted
*/
protected $hasReacted = array();
/**
* Has reacted?
*
@@ -368,30 +434,31 @@ trait Reactable
public function reacted( \IPS\Member $member = NULL )
{
$member = $member ?: \IPS\Member::loggedIn();
$idColumn = static::$databaseColumnId;
try
if( !isset( $this->hasReacted[ $member->member_id ] ) )
{
if ( is_array( $this->reputation ) )
$this->hasReacted[ $member->member_id ] = FALSE;
try
{
if ( isset( $this->reputation[ $member->member_id ] ) )
if ( is_array( $this->reputation ) )
{
return \IPS\Content\Reaction::load( $this->reputation[ $member->member_id ] );
if ( isset( $this->reputation[ $member->member_id ] ) )
{
$this->hasReacted[ $member->member_id ] = \IPS\Content\Reaction::load( $this->reputation[ $member->member_id ] );
}
}
else
{
return FALSE;
$where = $this->getReactionWhereClause( NULL, FALSE );
$where[] = array( 'member_id=?', $member->member_id );
$this->hasReacted[ $member->member_id ] = \IPS\Content\Reaction::load( \IPS\Db::i()->select( 'reaction', 'core_reputation_index', $where )->first() );
}
}
else
{
return \IPS\Content\Reaction::load( \IPS\Db::i()->select( 'reaction', 'core_reputation_index', array( "member_id=? AND rep_class=? AND type=? AND type_id=?", $member->member_id, get_class( $this ), static::reactionType(), $this->$idColumn ) )->first() );
}
}
catch( \UnderflowException $e )
{
return FALSE;
catch( \UnderflowException $e ){}
}
return $this->hasReacted[ $member->member_id ];
}
/**
@@ -413,51 +480,44 @@ trait Reactable
if ( count( $this->reactions() ) )
{
$idColumn = static::$databaseColumnId;
/* Figure out our app - we do it this way as content items and nodes will always have a lowercase namespace for the app, so if the match below fails, then 'core' can be assumed */
$app = explode( '\\', get_class( $this ) );
if ( \strtolower( $app[1] ) === $app[1] )
if ( is_array( $this->reputation ) )
{
$app = $app[1];
foreach( $this->reputation AS $memberId => $reaction )
{
if ( !isset( $this->reactBlurb[ $reaction ] ) )
{
$this->reactBlurb[ $reaction ] = 0;
}
$this->reactBlurb[ $reaction ]++;
}
}
else
{
$app = 'core';
}
foreach( \IPS\Db::i()->select( 'reaction', 'core_reputation_index', array( "app=? AND rep_class=? AND type=? AND type_id=?", $app, get_class( $this ), static::reactionType(), $this->$idColumn ) ) AS $rep )
{
if ( !isset( $this->reactBlurb[ $rep ] ) )
foreach( \IPS\Db::i()->select( 'reaction', 'core_reputation_index', $this->getReactionWhereClause() )->join( 'core_reactions', 'reaction=reaction_id' ) AS $rep )
{
$this->reactBlurb[ $rep ] = 0;
if ( !isset( $this->reactBlurb[ $rep ] ) )
{
$this->reactBlurb[ $rep ] = 0;
}
$this->reactBlurb[ $rep ]++;
}
$this->reactBlurb[ $rep ]++;
}
/* Error suppressor for https://bugs.php.net/bug.php?id=50688 */
@uksort( $this->reactBlurb, function( $a, $b ) {
try
{
$a = \IPS\Content\Reaction::load( $a );
$b = \IPS\Content\Reaction::load( $b );
}
/* One of the reactions does not exist */
catch( \OutOfRangeException $e )
{
return 1;
}
$enabledReactions = \IPS\Content\Reaction::enabledReactions();
@uksort( $this->reactBlurb, function( $a, $b ) use( $enabledReactions ) {
$positionA = $enabledReactions[ $a ]->position;
$positionB = $enabledReactions[ $b ]->position;
if ( $a->position < $b->position )
{
return -1;
}
elseif ( $a->position == $b->position )
if ( $positionA == $positionB )
{
return 0;
}
else
{
return 1;
}
return ( $positionA < $positionB ) ? -1 : 1;
} );
}
else
@@ -495,9 +555,9 @@ trait Reactable
}
/* Did anyone like it? */
$numberOfLikes = $this->reactionCount(); # int
$numberOfLikes = count( $this->reactions() ); # int
if ( $numberOfLikes )
{
{
/* Is it just us? */
$userLiked = ( $this->reacted() );
if ( $userLiked and $numberOfLikes < 2 )
@@ -521,11 +581,10 @@ trait Reactable
$peopleToDisplayInSecondaryView = array();
/* Some random names */
$idColumn = static::$databaseColumnId;
$i = 0;
$peopleToDisplayInSecondaryView = array();
/* Figure out our app - we do it this way as content items and nodes will always have a lowercase namespace for the app, so if the match below fails, then 'core' can be assumed */
$app = explode( '\\', get_class( $this ) );
$app = explode( '\\', static::reactionClass() );
if ( \strtolower( $app[1] ) === $app[1] )
{
$app = $app[1];
@@ -534,16 +593,18 @@ trait Reactable
{
$app = 'core';
}
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array( 'app=? AND type=? AND type_id=? AND member_id!=?', $app, static::reactionType(), $this->$idColumn, ( \IPS\Member::loggedIn()->member_id ) ?: 0 ), 'RAND()', $userLiked ? 17 : 18 ) as $rep )
$where = $this->getReactionWhereClause();
$where[] = array( 'member_id!=?', \IPS\Member::loggedIn()->member_id ?: 0 );
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', $where, 'RAND()', $userLiked ? 17 : 18 )->join( 'core_reactions', 'reaction=reaction_id' ) as $rep )
{
if ( $i < ( $userLiked ? 2 : 3 ) )
{
$peopleToDisplayInMainView[] = htmlspecialchars( \IPS\Member::load( $rep['member_id'] )->name, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE );
$peopleToDisplayInMainView[] = htmlspecialchars( \IPS\Member::load( $rep['member_id'] )->name, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE );
$andXOthers--;
}
else
{
$peopleToDisplayInSecondaryView[] = htmlspecialchars( \IPS\Member::load( $rep['member_id'] )->name, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE );
$peopleToDisplayInSecondaryView[] = htmlspecialchars( \IPS\Member::load( $rep['member_id'] )->name, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE );
}
$i++;
}
@@ -569,7 +630,7 @@ trait Reactable
$this->likeBlurb = '';
}
}
return $this->likeBlurb;
}
}