Version 5.0.13

This commit is contained in:
Neo committed 2025-12-19 19:04:57 -08:00
1 parent b89858b2ff
commit 5efe744c5f
1407 files changed
+69690 -30177

No files matched your search

+197 -10
View File
@@ -17,10 +17,13 @@ use Exception;
use IPS\Application\Module;
use IPS\Content\ClubContainer;
use IPS\Content\Comment;
use IPS\Content\Followable;
use IPS\Content\Item;
use IPS\Content\Permissions as PermissionsExtension;
use IPS\Content\Review;
use IPS\Content\Search\Index;
use IPS\Content\Search\SearchContent;
use IPS\Content\ViewUpdates;
use IPS\core\DeletionLog;
use IPS\Data\Store;
use IPS\DateTime;
@@ -60,6 +63,7 @@ use function is_array;
use function is_int;
use function is_null;
use function is_numeric;
use function mb_substr;
use function strlen;
use function substr;
@@ -74,6 +78,7 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*
* @mixin ClubContainer
* @mixin DelayedCount
* @mixin Grouping
* @mixin Statistics
* @mixin Colorize
* @mixin Permissions
@@ -218,7 +223,7 @@ abstract class Model extends ActiveRecord
* @param Member|null $member The member to check permissions for or NULL for the currently logged in member
* @param mixed $where Additional WHERE clause
* @param array|null $limit Limit/offset to use, or NULL for no limit (default)
* @return Model[]
* @return static[]
*/
public static function roots( ?string $permissionCheck='view', Member $member=NULL, mixed $where=array(), array $limit=NULL ): array
{
@@ -303,7 +308,11 @@ abstract class Model extends ActiveRecord
/* Permission check? */
if ( $permissionCheck )
{
$where[] = array( '(' . Db::i()->findInSet( 'core_permission_index.perm_' . static::$permissionMap[ $permissionCheck ], $member->permissionArray() ) . ' OR ' . 'core_permission_index.perm_' . static::$permissionMap[ $permissionCheck ] . '=? )', '*' );
foreach( PermissionsExtension::nodePermissionClause( $permissionCheck, get_called_class(), $member ) as $clause )
{
$where[] = $clause;
}
if ( static::$databaseColumnEnabledDisabled )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnEnabledDisabled . '=1' );
@@ -355,7 +364,10 @@ abstract class Model extends ActiveRecord
{
$member = $member ?: Member::loggedIn();
$where[] = array( '(' . Db::i()->findInSet( 'core_permission_index.perm_' . static::$permissionMap[ $permissionCheck ], $member->groups ) . ' OR ' . 'core_permission_index.perm_' . static::$permissionMap[ $permissionCheck ] . '=? )', '*' );
foreach( PermissionsExtension::nodePermissionClause( $permissionCheck, get_called_class(), $member ) as $clause )
{
$where[] = $clause;
}
if ( static::$databaseColumnEnabledDisabled )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnEnabledDisabled . '=1' );
@@ -524,9 +536,11 @@ abstract class Model extends ActiveRecord
$order = static::$databaseColumnOrder !== NULL ? static::$databasePrefix . static::$databaseColumnOrder : NULL;
if ( in_array( 'IPS\Node\Permissions', class_implements( get_called_class() ) ) and $permissionCheck !== NULL )
{
$permQueryWhere = PermissionsExtension::nodePermissionClause( $permissionCheck, get_called_class(), $member );
$permQueryWhere[] = [ 'core_permission_index.app=?', static::$permApp ];
$permQueryWhere[] = [ 'core_permission_index.perm_type=?', static::$permType ];
$where[] = array(
'(' . static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnId . ' IN(?) )',
Db::i()->select( 'perm_type_id', 'core_permission_index', array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND ( ( " . Db::i()->findInSet( 'perm_' . static::$permissionMap[ $permissionCheck ], $member->permissionArray() ) . ' ) OR perm_' . static::$permissionMap[ $permissionCheck ] . '=? )', static::$permApp, static::$permType, '*' ) )
'(' . static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnId . ' IN( ' . Db::i()->select( 'perm_type_id', 'core_permission_index', $permQueryWhere )->returnFullQuery() . ') )',
);
$select = Db::i()->select( '*', static::$databaseTable, $where, $order, NULL, NULL, NULL, Db::SELECT_MULTIDIMENSIONAL_JOINS );
@@ -1408,8 +1422,10 @@ abstract class Model extends ActiveRecord
/* Permission check? */
if( in_array( Permissions::class, class_implements( $this ) ) and $permissionCheck !== null )
{
$where[] = array( '(' . Db::i()->findInSet( 'perm_' . static::$permissionMap[ $permissionCheck ], $member->groups ) . ' OR ' . 'perm_' . static::$permissionMap[ $permissionCheck ] . '=? )', '*' );
foreach( PermissionsExtension::nodePermissionClause( $permissionCheck, get_class( $this ), $member, false ) as $clause )
{
$where[] = $clause;
}
if ( static::$databaseColumnEnabledDisabled )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnEnabledDisabled . '=1' );
@@ -1462,7 +1478,10 @@ abstract class Model extends ActiveRecord
if ( in_array( 'IPS\Node\Permissions', class_implements( $subnodeClass ) ) and $permissionCheck !== NULL )
{
/* @var $permissionMap array */
$where[] = array( '(' . Db::i()->findInSet( 'perm_' . $subnodeClass::$permissionMap[ $permissionCheck ], $member->groups ) . ' OR ' . 'perm_' . $subnodeClass::$permissionMap[ $permissionCheck ] . '=? )', '*' );
foreach( PermissionsExtension::nodePermissionClause( $permissionCheck, $subnodeClass, $member, false ) as $clause )
{
$where[] = $clause;
}
if ( $subnodeClass::$databaseColumnEnabledDisabled )
{
$where[] = array( $subnodeClass::$databasePrefix . $subnodeClass::$databaseColumnEnabledDisabled . '=1' );
@@ -1790,6 +1809,12 @@ abstract class Model extends ActiveRecord
*/
public function canDelete(): bool
{
/* Extensions go first */
if( static::$canBeExtended and $permCheck = PermissionsExtension::can( 'delete', $this ) )
{
return ( $permCheck === PermissionsExtension::PERM_ALLOW );
}
if ( $this->deleteOrMoveQueued() === TRUE )
{
return FALSE;
@@ -2334,10 +2359,16 @@ abstract class Model extends ActiveRecord
/**
* Get permissions
*
* @return void
* @return array|null
*/
public function permissions()
public function permissions() : array|null
{
/* If we don't use permissions, stop here */
if ( !in_array( 'IPS\Node\Permissions', class_implements( $this ) ) )
{
return null;
}
if ( $this->_permissions === NULL )
{
if( !isset( static::permissionStore()[ get_called_class() ][ $this->_id ] ) )
@@ -2530,6 +2561,15 @@ abstract class Model extends ActiveRecord
*/
public function can( mixed $permission, Group|Member $member=NULL, bool $considerPostBeforeRegistering = TRUE ): bool
{
/* Extensions go first, but only for viewing and commenting, not for modifying the node */
if( in_array( $permission, [ 'view', 'read', 'add', 'comment', 'reply', 'review' ] ) )
{
if( static::$canBeExtended and !( $member instanceof Group ) and $permCheck = PermissionsExtension::can( $permission, $this, $member ) )
{
return ( $permCheck === PermissionsExtension::PERM_ALLOW );
}
}
$_key = md5( $permission . ( $member ? ( ( $member instanceof Group ) ? $member->g_id : $member->member_id . '-' . $member->member_group_id ) : Member::loggedIn()->member_id . '-' . Member::loggedIn()->member_group_id ) . (int) $considerPostBeforeRegistering );
if( isset( $this->cachedPermissionChecks[ $_key ] ) )
@@ -3547,6 +3587,21 @@ abstract class Model extends ActiveRecord
}
}
/* Delete Node Groups */
if( IPS::classUsesTrait( $this, Grouping::class ) )
{
Db::i()->delete( 'core_node_groups_nodes', [
[ 'node_id=?', $this->id ],
[ Db::i()->in( 'group_id', array_keys( static::availableNodeGroups() ) ) ]
]);
}
/* Pending view updates */
if( IPS::classUsesTrait( get_called_class(), ViewUpdates::class ) )
{
Db::i()->delete( 'core_view_updates', [ 'classname=? and id=?', get_called_class(), $this->_id ] );
}
parent::delete();
Event::fire( 'onDelete', $this );
@@ -3651,6 +3706,47 @@ abstract class Model extends ActiveRecord
}
}
/**
* Follow this object
*
* @param string $frequency ( 'none', 'immediate', 'daily', 'weekly' )
* @param bool $public
* @param Member|null $member
* @return void
*/
public function follow( string $frequency, bool $public=true, ?Member $member=null ) : void
{
if( isset( static::$contentItemClass ) )
{
$itemClass = static::$contentItemClass;
if( IPS::classUsesTrait( $itemClass, Followable::class ) )
{
$itemClass::staticFollow( $this, $frequency, $public, $member );
/* Achievements */
Member::loggedIn()->achievementAction( 'core', 'FollowNode', $this );
}
}
}
/**
* Unfollow a node
*
* @param Member|null $member
* @return void
*/
public function unfollow( ?Member $member=null ) : void
{
if( isset( static::$contentItemClass ) )
{
$itemClass = static::$contentItemClass;
if( IPS::classUsesTrait( $itemClass, Followable::class ) )
{
$itemClass::containerUnfollow( $this, $member );
}
}
}
/* !ACP forms */
/**
@@ -3681,6 +3777,24 @@ abstract class Model extends ActiveRecord
{
$this->addBadgeFieldsToForm( $form, $this->getRecordBadge() );
}
/* If the form initially had one tab, and now it has additional tabs (because of extensions),
make sure that the first tab has a key and is not an empty string */
$tabs = array_keys( $form->elements );
if( count( $tabs ) > 1 and $tabs[0] == '' )
{
$originalElements = $form->elements;
$form->elements = [
'__default_tab_settings' => $originalElements['']
];
foreach( $originalElements as $k => $v )
{
if( $k != '' )
{
$form->elements[ $k ] = $v;
}
}
}
}
/**
@@ -4219,4 +4333,77 @@ abstract class Model extends ActiveRecord
}
return $response;
}
/**
* Normalise IDs from stored data, which may be a string, array, numeric or even a node group
*
* @param mixed $ids
* @return array
*/
public static function normalizeIds( mixed $ids ): array
{
/* Is this a comma-separated list? */
if( is_string( $ids ) and mb_strpos( $ids, ',' ) !== false )
{
$ids = array_filter( explode( ',', $ids ) );
}
if( is_array( $ids ) )
{
foreach( $ids as $id )
{
if ( ! is_numeric( $id ) and mb_substr( $id, 0, 1 ) === 'g' )
{
/* Is this a node group? */
if( IPS::classUsesTrait( static::class, Grouping::class ) )
{
$groupId = mb_substr( $id, 1, 1 );
$group = static::availableNodeGroups();
if( isset( $group[ $groupId ] ) )
{
/* Yeah, so grab the real IDs */
$ids = array_merge( $ids, $group[ $groupId ]->nodes );
}
}
}
}
return $ids;
}
else if( is_numeric( $ids ) )
{
return array( $ids );
}
else if( is_string( $ids ) )
{
if ( mb_substr( $ids, 0, 1 ) == 'g' )
{
/* Is this a node group? */
if( IPS::classUsesTrait( static::class, Grouping::class ) )
{
$groupId = mb_substr( $ids, 1, 1 );
$group = static::availableNodeGroups();
if( isset( $group[ $groupId ] ) )
{
/* Yeah, so grab the real IDs */
return $group[ $groupId ]->nodes;
}
else
{
return array();
}
}
else
{
return array();
}
}
return explode( ',', $ids );
}
return array();
}
}