Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+103 -54
View File
@@ -11,7 +11,7 @@
namespace IPS\Content;
/* To prevent PHP errors (extending class does not exist) revealing path */
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' );
exit;
@@ -39,14 +39,14 @@ trait Reactable
*/
public static function reactionClass()
{
return get_called_class();
return \get_called_class();
}
/**
* React
*
* @param \IPS\core\Reaction THe reaction
* @param \IPS\Member The member reacting, or NULL
* @param \IPS\core\Reaction $reaction The reaction
* @param \IPS\Member $member The member reacting, or NULL
* @return void
* @throws \DomainException
*/
@@ -82,7 +82,7 @@ trait Reactable
}
/* 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( '\\', \get_class( $this ) );
if ( \strtolower( $app[1] ) === $app[1] )
{
$app = $app[1];
@@ -150,7 +150,7 @@ trait Reactable
/**
* Remove Reaction
*
* @param \IPS\Member|NULL The member, or NULL
* @param \IPS\Member|NULL $member The member, or NULL for currently logged in member
* @return void
*/
public function removeReaction( \IPS\Member $member = NULL )
@@ -180,27 +180,28 @@ trait Reactable
throw new \DomainException;
}
if ( $memberReceived->member_id )
if( \IPS\Db::i()->delete( 'core_reputation_index', array( "app=? AND type=? AND type_id=? AND member_id=?", static::$application, static::reactionType(), $this->$idColumn, $member->member_id ) ) )
{
$memberReceived->pp_reputation_points = $memberReceived->pp_reputation_points - $reaction->value;
$memberReceived->save();
}
\IPS\Db::i()->delete( 'core_reputation_index', array( "id=?", $rep['id'] ) );
if ( $memberReceived->member_id )
{
$memberReceived->pp_reputation_points = $memberReceived->pp_reputation_points - $reaction->value;
$memberReceived->save();
}
/* Remove Notifications */
$memberIds = array();
/* 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 $memberToRecount )
{
$memberIds[ $memberToRecount ] = $memberToRecount;
}
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 $memberToRecount )
{
$memberIds[ $memberToRecount ] = $memberToRecount;
}
\IPS\Db::i()->delete( 'core_notifications', array( 'notification_key=? AND item_class=? AND item_id=?', 'new_likes', (string) get_class( $this ), (int) $this->$idColumn ) );
\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 $memberToRecount )
{
\IPS\Member::load( $memberToRecount )->recountNotifications();
foreach( $memberIds as $memberToRecount )
{
\IPS\Member::load( $memberToRecount )->recountNotifications();
}
}
/* Reset some cached values */
@@ -216,7 +217,7 @@ trait Reactable
/**
* Can React
*
* @param \IPS\Member|NULL The member, or NULL for currently logged in
* @param \IPS\Member|NULL $member The member, or NULL for currently logged in
* @return bool
*/
public function canReact( \IPS\Member $member = NULL )
@@ -273,7 +274,6 @@ trait Reactable
/**
* Reactions
*
* @param array|NULL $mixInData If the data is already know, it can be passed here to be manually set
* @return array
*/
public function reactions()
@@ -288,7 +288,7 @@ trait Reactable
$idColumn = static::$databaseColumnId;
$this->_reactions = array();
if ( is_array( $this->reputation ) )
if ( \is_array( $this->reputation ) )
{
if ( $enabledReactions = \IPS\Content\Reaction::enabledReactions() )
{
@@ -341,7 +341,7 @@ trait Reactable
/**
* Reaction Where Clause
*
* @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 \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, an 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
*/
@@ -357,7 +357,7 @@ trait Reactable
if ( $reactions !== NULL )
{
if ( !is_array( $reactions ) )
if ( !\is_array( $reactions ) )
{
$reactions = array( $reactions );
}
@@ -375,7 +375,7 @@ trait Reactable
}
}
if ( count( $in ) )
if ( \count( $in ) )
{
$where[] = array( \IPS\Db::i()->in( 'reaction', $in ) );
}
@@ -387,6 +387,7 @@ trait Reactable
/**
* Reaction Table
*
* @param \IPS\Content\Reaction|int|NULL $reaction This can be any one of the following: An \IPS\Content\Reaction object, an integer, or NULL
* @return \IPS\Helpers\Table\Db
*/
public function reactionTable( $reaction=NULL )
@@ -405,6 +406,28 @@ trait Reactable
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'reactionLog' );
$table->joins = array( array( 'from' => 'core_reactions', 'where' => 'reaction=reaction_id' ) );
$table->parsers = array(
'rep_date' => function( $date, $row )
{
if ( isset( \IPS\Request::i()->item ) and \IPS\Request::i()->item )
{
/* This is an item level thing, and not a comment level thing */
try
{
$class = $row['rep_class'];
$comment = $class::load( $row['type_id'] );
return \IPS\Member::loggedIn()->language()->addToStack( '_defart_from_date', NULL, array( 'htmlsprintf' => array( $comment->url(), $comment::$title, \IPS\DateTime::ts( $date )->html() ) ) );
}
catch( \Exception $e )
{
return \IPS\DateTime::ts( $date )->html();
}
}
return \IPS\DateTime::ts( $date )->html();
}
);
$table->rowButtons = function( $row )
{
return array(
@@ -428,7 +451,7 @@ trait Reactable
/**
* Has reacted?
*
* @param \IPS\Member|NULL The member, or NULL for currently logged in
* @param \IPS\Member|NULL $member The member, or NULL for currently logged in
* @return \IPS\Content\Reaction|FALSE
*/
public function reacted( \IPS\Member $member = NULL )
@@ -441,7 +464,7 @@ trait Reactable
try
{
if ( is_array( $this->reputation ) )
if ( \is_array( $this->reputation ) )
{
if ( isset( $this->reputation[ $member->member_id ] ) )
{
@@ -477,19 +500,22 @@ trait Reactable
{
$this->reactBlurb = array();
if ( count( $this->reactions() ) )
if ( \count( $this->reactions() ) )
{
$idColumn = static::$databaseColumnId;
if ( is_array( $this->reputation ) )
if ( \is_array( $this->_reactions ) )
{
foreach( $this->reputation AS $memberId => $reaction )
foreach( $this->_reactions AS $memberId => $reactions )
{
if ( !isset( $this->reactBlurb[ $reaction ] ) )
foreach( $reactions as $reaction )
{
$this->reactBlurb[ $reaction ] = 0;
if ( !isset( $this->reactBlurb[ $reaction ] ) )
{
$this->reactBlurb[ $reaction ] = 0;
}
$this->reactBlurb[ $reaction ]++;
}
$this->reactBlurb[ $reaction ]++;
}
}
else
@@ -504,7 +530,7 @@ trait Reactable
$this->reactBlurb[ $rep ]++;
}
}
/* Error suppressor for https://bugs.php.net/bug.php?id=50688 */
$enabledReactions = \IPS\Content\Reaction::enabledReactions();
@@ -536,7 +562,7 @@ trait Reactable
/**
* Who Reacted
*
* @param bool|NULL Use like text instead? NULL to automatically determine
* @param bool|NULL $isLike Use like text instead? NULL to automatically determine
* @return string
*/
public function whoReacted( $isLike = NULL )
@@ -555,7 +581,7 @@ trait Reactable
}
/* Did anyone like it? */
$numberOfLikes = count( $this->reactions() ); # int
$numberOfLikes = \count( $this->reactions() ); # int
if ( $numberOfLikes )
{
/* Is it just us? */
@@ -564,26 +590,22 @@ trait Reactable
{
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack("{$langPrefix}blurb_just_you");
}
/* Nope, we need to display a number... */
else
{
$peopleToDisplayInMainView = array();
$peopleToDisplayInSecondaryView = array(); // This is only used for "like" mode (i.e. there's only 1 reputation type)
$andXOthers = $numberOfLikes;
/* If the user liked, we always show "You" first */
if ( $userLiked )
{
$peopleToDisplayInMainView[] = \IPS\Member::loggedIn()->language()->addToStack("{$langPrefix}blurb_you_and_others");
$andXOthers--;
}
$peopleToDisplayInSecondaryView = array();
/* Some random names */
$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( '\\', static::reactionClass() );
if ( \strtolower( $app[1] ) === $app[1] )
{
@@ -595,11 +617,11 @@ trait Reactable
}
$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 )
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', $where, 'RAND()', \IPS\Content\Reaction::isLikeMode() ? 18 : ( $userLiked ? 2 : 3 ) )->join( 'core_reactions', 'reaction=reaction_id' ) as $rep )
{
if ( $i < ( $userLiked ? 2 : 3 ) )
{
$peopleToDisplayInMainView[] = htmlspecialchars( \IPS\Member::load( $rep['member_id'] )->name, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE );
$peopleToDisplayInMainView[] = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( \IPS\Member::load( $rep['member_id'] ) );
$andXOthers--;
}
else
@@ -610,19 +632,18 @@ trait Reactable
}
/* If there's people to display in the secondary view, add that */
if ( $peopleToDisplayInSecondaryView )
if ( $andXOthers )
{
if ( count( $peopleToDisplayInSecondaryView ) < $andXOthers )
if ( \count( $peopleToDisplayInSecondaryView ) < $andXOthers )
{
$peopleToDisplayInSecondaryView[] = \IPS\Member::loggedIn()->language()->addToStack( "{$langPrefix}blurb_others_secondary", FALSE, array( 'pluralize' => array( $andXOthers - count( $peopleToDisplayInSecondaryView ) ) ) );
$peopleToDisplayInSecondaryView[] = \IPS\Member::loggedIn()->language()->addToStack( "{$langPrefix}blurb_others_secondary", FALSE, array( 'pluralize' => array( $andXOthers - \count( $peopleToDisplayInSecondaryView ) ) ) );
}
$peopleToDisplayInMainView[] = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->reputationOthers( $this->url( 'showReactions' ), \IPS\Member::loggedIn()->language()->addToStack( "{$langPrefix}blurb_others", FALSE, array( 'pluralize' => array( $andXOthers ) ) ), json_encode( $peopleToDisplayInSecondaryView ) );
}
/* Put it all together */
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack( "{$langPrefix}blurb", FALSE, array( 'pluralize' => array( $numberOfLikes ), 'htmlsprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $peopleToDisplayInMainView ) ) ) );
}
}
}
/* Nobody liked it - show nothing */
else
@@ -633,4 +654,32 @@ trait Reactable
return $this->likeBlurb;
}
/**
* Return boolean indicating if *any* reaction elements are available to the user
* Provides a convenient way of reducing logic in frontend templates rather than having to check every condition.
*
* @return boolean
*/
public function hasReactionBar()
{
/* If we're in count mode and have > 0 */
if ( \IPS\Settings::i()->reaction_count_display == 'count' && $this->reactionCount() )
{
return TRUE;
}
/* Not in count mode and have some blurb to show */
if( \IPS\Settings::i()->reaction_count_display !== 'count' && $this->reactBlurb() && \count( $this->reactBlurb() ) )
{
return TRUE;
}
if( $this->canReact() )
{
return TRUE;
}
return FALSE;
}
}