Version 5.0.13
This commit is contained in:
1 parent
b89858b2ff
commit
5efe744c5f
1407 files changed
+69690
-30177
No files matched your search
@@ -145,6 +145,15 @@ class Controller extends DispatcherController
|
||||
|
||||
$tree = new Tree( $this->url, $nodeClass::$nodeTitle, array( $this, '_getRoots' ), array( $this, '_getRow' ), array( $this, '_getRowParentId' ), array( $this, '_getChildren' ), array( $this, '_getRootButtons' ), TRUE, $this->lockParents, $this->protectRoots, $this->_getRootsPerPage(), array( $this, '_getTotalRoots' ) );
|
||||
Output::i()->output .= $tree;
|
||||
|
||||
if( IPS::classUsesTrait( $nodeClass, Grouping::class ) )
|
||||
{
|
||||
Output::i()->sidebar['actions']['groups'] = [
|
||||
'icon' => 'folder-open',
|
||||
'title' => 'node_groups',
|
||||
'link' => $this->url->setQueryString( 'do', 'nodeGroups' )
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1691,4 +1700,187 @@ class Controller extends DispatcherController
|
||||
{
|
||||
return $node->_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage Node Groups
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function nodeGroups() : void
|
||||
{
|
||||
$nodeClass = $this->nodeClass;
|
||||
|
||||
$tree = new Tree(
|
||||
$this->url->setQueryString( 'do', 'nodeGroups' ),
|
||||
NodeGroup::$nodeTitle,
|
||||
array( $this, '_getNodeGroupRoots' ),
|
||||
array( $this, '_getNodeGroupRow' ),
|
||||
null,
|
||||
null,
|
||||
function(){
|
||||
return [
|
||||
'add' => [
|
||||
'icon' => 'plus',
|
||||
'title' => 'new_node_group',
|
||||
'link' => $this->url->setQueryString( 'do', 'nodeGroupForm' ),
|
||||
'data' => [ 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack( 'new_node_group' ) ]
|
||||
]
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
/* Figure out the header blurb */
|
||||
try
|
||||
{
|
||||
$langStringPlural = Member::loggedIn()->language()->get( '__defart_nodegroup__' . $nodeClass->application . '_pl' );
|
||||
$langString = Member::loggedIn()->language()->get( '__defart_nodegroup__' . $nodeClass->application );
|
||||
}
|
||||
catch( \Exception )
|
||||
{
|
||||
$langStringPlural = Member::loggedIn()->language()->get( '__defart_nodegroup__core_pl' );
|
||||
$langString = Member::loggedIn()->language()->get( '__defart_nodegroup__core' );
|
||||
}
|
||||
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack( NodeGroup::$nodeTitle );
|
||||
Output::i()->breadcrumb[] = [ $this->url, $this->title ];
|
||||
Output::i()->breadcrumb[] = [ null, Member::loggedIn()->language()->addToStack( NodeGroup::$nodeTitle ) ];
|
||||
Output::i()->output = Theme::i()->getTemplate( 'forms','core' )->blurb( Member::loggedIn()->language()->addToStack( 'node_groups_header_desc', null, [ 'sprintf' => [ $langStringPlural, $langString ] ] ) );
|
||||
Output::i()->output .= $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all node groups for this node class
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function _getNodeGroupRoots() : array
|
||||
{
|
||||
$roots = [];
|
||||
foreach( NodeGroup::roots() as $node )
|
||||
{
|
||||
if( $node->class == $this->nodeClass )
|
||||
{
|
||||
$roots[ $node->_id ] = $this->_getNodeGroupRow( $node );
|
||||
}
|
||||
}
|
||||
return $roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted node group row
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return string
|
||||
*/
|
||||
public function _getNodeGroupRow( mixed $id ) : string
|
||||
{
|
||||
if ( $id instanceof Model)
|
||||
{
|
||||
$node = $id;
|
||||
$id = $node->_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
$node = NodeGroup::load( $id );
|
||||
}
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
Output::i()->error( 'node_error', '2S101/P', 404, '' );
|
||||
}
|
||||
}
|
||||
|
||||
$buttons = [
|
||||
'edit' => [
|
||||
'icon' => 'pencil',
|
||||
'title' => 'edit',
|
||||
'link' => $this->url->setQueryString( [ 'do' => 'nodeGroupForm', 'id' => $node->_id ] ),
|
||||
'data' => [ 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack( 'edit' ) ]
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'times-circle',
|
||||
'title' => 'delete',
|
||||
'link' => $this->url->setQueryString( [ 'do' => 'nodeGroupDelete', 'id' => $node->_id ] )->csrf(),
|
||||
'data' => [ 'delete' => '' ]
|
||||
]
|
||||
];
|
||||
|
||||
return Theme::i()->getTemplate( 'trees', 'core' )->row(
|
||||
$this->url->setQueryString( 'do', 'nodeGroups' ),
|
||||
$id,
|
||||
static::nodeTitle( $node ),
|
||||
false,
|
||||
$buttons
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/Edit a node group
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function nodeGroupForm() : void
|
||||
{
|
||||
$group = null;
|
||||
if( isset( Request::i()->id ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$group = NodeGroup::load( Request::i()->id );
|
||||
if( $group->class != $this->nodeClass )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
|
||||
if( $group === null )
|
||||
{
|
||||
$group = new NodeGroup;
|
||||
$group->class = $this->nodeClass;
|
||||
}
|
||||
|
||||
$form = new Form;
|
||||
$group->form( $form );
|
||||
if( $values = $form->values() )
|
||||
{
|
||||
$group->saveForm( $group->formatFormValues( $values ) );
|
||||
|
||||
Output::i()->redirect( $this->url->setQueryString( 'do', 'nodeGroups' ) );
|
||||
}
|
||||
|
||||
/* Appify the language strings */
|
||||
try
|
||||
{
|
||||
$langString = Member::loggedIn()->language()->get( '__defart_nodegroup__' . $this->nodeClass->application );
|
||||
}
|
||||
catch( \Exception )
|
||||
{
|
||||
$langString = Member::loggedIn()->language()->get( '__defart_nodegroup__core' );
|
||||
}
|
||||
|
||||
Member::loggedIn()->language()->words['node_group_name_desc'] = Member::loggedIn()->language()->addToStack( 'node_group_name__desc', null, [ 'sprintf' => [ $langString ] ] );
|
||||
|
||||
Output::i()->output = (string) $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a node group
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function nodeGroupDelete() : void
|
||||
{
|
||||
Request::i()->confirmedDelete();
|
||||
|
||||
try
|
||||
{
|
||||
NodeGroup::load( Request::i()->id )->delete();
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
|
||||
Output::i()->redirect( $this->url->setQueryString( 'do', 'nodeGroups' ) );
|
||||
}
|
||||
}
|
||||
+87
-60
@@ -22,6 +22,7 @@ use IPS\Helpers\Form\Icon;
|
||||
use IPS\Helpers\Form\Number;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Theme;
|
||||
use UnderflowException;
|
||||
@@ -43,27 +44,42 @@ trait CustomBadge
|
||||
/**
|
||||
* Get the custom badge linked to this class
|
||||
*
|
||||
* @param string|null $identifier
|
||||
* @return CustomBadgeClass|null
|
||||
*/
|
||||
public function getRecordBadge() : CustomBadgeClass|null
|
||||
public function getRecordBadge( ?string $identifier=null ) : CustomBadgeClass|null
|
||||
{
|
||||
$class = get_called_class();
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$key = $this->$idColumn . '.' . ( $identifier ?? '' );
|
||||
|
||||
if( !isset( static::$customBadgeStore[ $class ][ $this->$idColumn ] ) )
|
||||
if( !isset( static::$customBadgeStore[ $class ][ $key ] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$row = Db::i()->select( '*', 'core_custom_badges', [ 'class=? and active_record_id=?', $class, $this->$idColumn ] )->first();
|
||||
static::$customBadgeStore[ $class ][ $this->$idColumn ] = CustomBadgeClass::constructFromData( $row );
|
||||
$where = [
|
||||
[ 'class=?', $class ],
|
||||
[ 'active_record_id=?', $this->$idColumn ]
|
||||
];
|
||||
if( !empty( $identifier ) )
|
||||
{
|
||||
$where[] = [ 'extra=?', $identifier ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = [ 'extra is null' ];
|
||||
}
|
||||
|
||||
$row = Db::i()->select( '*', 'core_custom_badges', $where )->first();
|
||||
static::$customBadgeStore[ $class ][ $key ] = CustomBadgeClass::constructFromData( $row );
|
||||
}
|
||||
catch( UnderflowException )
|
||||
{
|
||||
static::$customBadgeStore[ $class ][ $this->$idColumn ] = false;
|
||||
static::$customBadgeStore[ $class ][ $key ] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return static::$customBadgeStore[ $class ][ $this->$idColumn ] ?: null;
|
||||
return static::$customBadgeStore[ $class ][ $key ] ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,84 +87,94 @@ trait CustomBadge
|
||||
*
|
||||
* @param Form $form
|
||||
* @param CustomBadgeClass|null $badge
|
||||
* @param string|null $badgeType
|
||||
* @return void
|
||||
*/
|
||||
public function addBadgeFieldsToForm( Form $form, ?CustomBadgeClass $badge=null ) : void
|
||||
public function addBadgeFieldsToForm( Form $form, ?CustomBadgeClass $badge=null, ?string $badgeType=null ) : void
|
||||
{
|
||||
$form->class .= " ipsBadgePicker--form";
|
||||
$badgeType = $badgeType ?? 'custombadge';
|
||||
|
||||
$shapeToggles = ["custombadge_background", "custombadge_border"];
|
||||
$dynamicShapeToggles = [ "custombadge_sides", "custombadge_rotation" ];
|
||||
$shapeToggles = ["{$badgeType}_background", "{$badgeType}_border"];
|
||||
$dynamicShapeToggles = [ "{$badgeType}_sides", "{$badgeType}_rotation" ];
|
||||
$form->add( new YesNo(
|
||||
"custombadge_use_custom",
|
||||
"{$badgeType}_use_custom",
|
||||
(bool) $badge,
|
||||
true,
|
||||
[
|
||||
'togglesOff' => static::$customBadgeToggles ?? [],
|
||||
'togglesOn' => [
|
||||
"{$form->id}_header_badge_preview",
|
||||
"custombadge_border",
|
||||
"custombadge_foreground",
|
||||
"custombadge_background",
|
||||
"custombadge_shape",
|
||||
"custombadge_icon",
|
||||
"custombadge_sides",
|
||||
"custombadge_rotation",
|
||||
"custombadge_number_overlay",
|
||||
"custombadge_icon_size",
|
||||
'preview'
|
||||
"{$form->id}_header_{$badgeType}_preview",
|
||||
"{$badgeType}_preview",
|
||||
"{$badgeType}_border",
|
||||
"{$badgeType}_foreground",
|
||||
"{$badgeType}_background",
|
||||
"{$badgeType}_shape",
|
||||
"{$badgeType}_icon",
|
||||
"{$badgeType}_sides",
|
||||
"{$badgeType}_rotation",
|
||||
"{$badgeType}_number_overlay",
|
||||
"{$badgeType}_icon_size"
|
||||
]
|
||||
]
|
||||
));
|
||||
|
||||
$form->addHeader( 'custombadge_preview' );
|
||||
$form->addHeader( $badgeType . '_preview' );
|
||||
Member::loggedIn()->language()->words[ $badgeType . '_preview' ] = Member::loggedIn()->language()->get( 'custombadge_preview' );
|
||||
|
||||
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'admin_badgepreview.js', 'core', 'admin' ) );
|
||||
$form->addHtml( Theme::i()->getTemplate( 'achievements', 'core', 'admin' )->badgePreview( $badge ? $badge->raw : '' ) );
|
||||
$form->addHtml( Theme::i()->getTemplate( 'achievements', 'core', 'admin' )->badgePreview( $badge ? $badge->raw : '', $badgeType ) );
|
||||
|
||||
$form->add( new Radio( "custombadge_shape", $badge ? $badge->shape : 'circle', null, ['options' => [
|
||||
'square' => 'custombadge_square',
|
||||
'circle' => 'custombadge_circle',
|
||||
'ngon' => 'custombadge_n_gon',
|
||||
'star' => 'custombadge_star',
|
||||
'flower' => 'custombadge_flower',
|
||||
"no_shape" => "custombadge_no_shape",
|
||||
], 'toggles' => [
|
||||
'ngon' => [...$dynamicShapeToggles, ...$shapeToggles],
|
||||
'star' => [...$dynamicShapeToggles, ...$shapeToggles],
|
||||
'flower' => [...$dynamicShapeToggles, ...$shapeToggles],
|
||||
"square" => $shapeToggles,
|
||||
"circle" => $shapeToggles
|
||||
], 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-shape']], null, null, null, "custombadge_shape") );
|
||||
|
||||
$form->add( new Icon( "custombadge_icon", ( $badge and $badge->icon ) ? [ $badge->icon ] : null, false, ['useSvgIcon' => true, 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-icon']], null, null, null, "custombadge_icon" ) );
|
||||
$formFields = [
|
||||
'shape' => new Radio( "{$badgeType}_shape", $badge ? $badge->shape : 'circle', null, ['options' => [
|
||||
'square' => 'custombadge_square',
|
||||
'circle' => 'custombadge_circle',
|
||||
'ngon' => 'custombadge_n_gon',
|
||||
'star' => 'custombadge_star',
|
||||
'flower' => 'custombadge_flower',
|
||||
"no_shape" => "custombadge_no_shape",
|
||||
], 'toggles' => [
|
||||
'ngon' => [...$dynamicShapeToggles, ...$shapeToggles],
|
||||
'star' => [...$dynamicShapeToggles, ...$shapeToggles],
|
||||
'flower' => [...$dynamicShapeToggles, ...$shapeToggles],
|
||||
"square" => $shapeToggles,
|
||||
"circle" => $shapeToggles
|
||||
], 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-shape']], null, null, null, "{$badgeType}_shape"),
|
||||
'icon' => new Icon( "{$badgeType}_icon", ( $badge and $badge->icon ) ? [ $badge->icon ] : null, false, ['useSvgIcon' => true, 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-icon']], null, null, null, "{$badgeType}_icon" )
|
||||
];
|
||||
|
||||
/* The overlay is optional */
|
||||
if ( !isset( static::$customBadgeNumberOverlay ) or static::$customBadgeNumberOverlay )
|
||||
{
|
||||
$form->add( new Number( "custombadge_number_overlay", ( $badge and $badge->number_overlay ) ? $badge->number_overlay : 0, false, [ 'step' => 1, 'min' => 1, 'max' => 999, 'unlimited' => 0, 'unlimitedLang' => 'none', 'rowClasses' => ['ipsFieldRow--badge-creator'] ], null, null, null, "custombadge_number_overlay" ) );
|
||||
$formFields['number_overlay'] = new Number( "{$badgeType}_number_overlay", ( $badge and $badge->number_overlay ) ? $badge->number_overlay : 0, false, [ 'step' => 1, 'min' => 1, 'max' => 999, 'unlimited' => 0, 'unlimitedLang' => 'none', 'rowClasses' => ['ipsFieldRow--badge-creator'] ], null, null, null, "{$badgeType}_number_overlay" );
|
||||
}
|
||||
|
||||
$form->add( new Color( "custombadge_background", $badge ? $badge->background : '#eeb95f', null, ['rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-background']], null, null, null, "custombadge_background" ) );
|
||||
$form->add( new Color( "custombadge_border", $badge ? $badge->border : '#f7d36f', null, ['rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-border']], null, null, null, "custombadge_border" ) );
|
||||
$form->add( new Color( "custombadge_foreground", $badge ? $badge->foreground : '#ffffff', null, ['rgba' => true, 'swatches' => true, 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-foreground']], null, null, null, "custombadge_foreground" ) );
|
||||
$formFields['background'] = new Color( "{$badgeType}_background", $badge ? $badge->background : '#eeb95f', null, ['rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-background']], null, null, null, "{$badgeType}_background" );
|
||||
$formFields['border'] = new Color( "{$badgeType}_border", $badge ? $badge->border : '#f7d36f', null, ['rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-border']], null, null, null, "{$badgeType}_border" );
|
||||
$formFields['foreground'] = new Color( "{$badgeType}_foreground", $badge ? $badge->foreground : '#ffffff', null, ['rgba' => true, 'swatches' => true, 'rowClasses' => ['ipsFieldRow--badge-creator ipsFieldRow--badge-creator-foreground']], null, null, null, "{$badgeType}_foreground" );
|
||||
$formFields['icon_size'] = new Form\Number( "{$badgeType}_icon_size", $badge ? $badge->icon_size : 3, null, ["min" => 1, "max" => 5, "step" => 1, "range" => true, "rowClasses" => ['ipsFieldRow--badge-creator', 'ipsFieldRow--badge-creator-icon-size']], id: "{$badgeType}_icon_size" );
|
||||
$formFields['sides'] = new Number( "{$badgeType}_sides", $badge ? $badge->sides : 5, null, ['step' => 1, 'min' => 4, 'max' => 12, 'range' => true, 'rowClasses' => ['ipsFieldRow--badge-creator']], null, null, null, "{$badgeType}_sides" );
|
||||
$formFields['rotation'] = new Number( "{$badgeType}_rotation", $badge?->rotation, null, ['step' => 1, 'min' => 0, 'max' => 90, 'range' => true, 'rowClasses' => ['ipsFieldRow--badge-creator']], null, null, null, "{$badgeType}_rotation" );
|
||||
|
||||
$form->add( new Form\Number( "custombadge_icon_size", $badge ? $badge->icon_size : 3, null, ["min" => 1, "max" => 5, "step" => 1, "range" => true, "rowClasses" => ['ipsFieldRow--badge-creator', 'ipsFieldRow--badge-creator-icon-size']], id: "custombadge_icon_size" ) );
|
||||
|
||||
$form->add( new Number( "custombadge_sides", $badge ? $badge->sides : 5, null, ['step' => 1, 'min' => 4, 'max' => 12, 'range' => true, 'rowClasses' => ['ipsFieldRow--badge-creator']], null, null, null, "custombadge_sides" ) );
|
||||
$form->add( new Number( "custombadge_rotation", $badge?->rotation, null, ['step' => 1, 'min' => 0, 'max' => 90, 'range' => true, 'rowClasses' => ['ipsFieldRow--badge-creator']], null, null, null, "custombadge_rotation" ) );
|
||||
foreach( $formFields as $k => $v )
|
||||
{
|
||||
$v->label = Member::loggedIn()->language()->addToStack( 'custombadge_' . $k );
|
||||
$form->add( $v );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
* @param string|null $badgeType
|
||||
* @return void
|
||||
*/
|
||||
public function saveRecordBadge( array &$values ) : void
|
||||
public function saveRecordBadge( array &$values, ?string $badgeType=null ) : void
|
||||
{
|
||||
$badge = $this->getRecordBadge();
|
||||
$badge = $this->getRecordBadge( $badgeType );
|
||||
$prefix = $badgeType ?? 'custombadge';
|
||||
|
||||
/* Are we using a badge? */
|
||||
if( $values['custombadge_use_custom'] )
|
||||
if( $values[ $prefix . '_use_custom'] )
|
||||
{
|
||||
if( $badge === null )
|
||||
{
|
||||
@@ -159,15 +185,16 @@ trait CustomBadge
|
||||
$badge->active_record_id = $this->$idColumn;
|
||||
}
|
||||
|
||||
$badge->shape = $values['custombadge_shape'];
|
||||
$badge->foreground = $values['custombadge_foreground'];
|
||||
$badge->background = $values['custombadge_background'];
|
||||
$badge->border = $values['custombadge_border'];
|
||||
$badge->icon = $values['custombadge_icon'][0] ?? null;
|
||||
$badge->rotation = $values['custombadge_rotation'];
|
||||
$badge->sides = $values['custombadge_sides'];
|
||||
$badge->number_overlay = $values['custombadge_number_overlay'] ?? 0;
|
||||
$badge->icon_size = $values['custombadge_icon_size'] ?? 3;
|
||||
$badge->shape = $values[ $prefix . '_shape'];
|
||||
$badge->foreground = $values[ $prefix . '_foreground'];
|
||||
$badge->background = $values[ $prefix . '_background'];
|
||||
$badge->border = $values[ $prefix . '_border'];
|
||||
$badge->icon = $values[ $prefix . '_icon'][0] ?? null;
|
||||
$badge->rotation = $values[ $prefix . '_rotation'];
|
||||
$badge->sides = $values[ $prefix . '_sides'];
|
||||
$badge->number_overlay = $values[ $prefix . '_number_overlay'] ?? 0;
|
||||
$badge->icon_size = $values[ $prefix . '_icon_size'] ?? 3;
|
||||
$badge->extra = $badgeType ?? null;
|
||||
$badge->save();
|
||||
}
|
||||
elseif( $badge !== null )
|
||||
@@ -179,7 +206,7 @@ trait CustomBadge
|
||||
/* Clear the form fields */
|
||||
foreach ( ['shape', 'foreground', 'background', 'border', 'icon', 'rotation', 'sides', 'number_overlay', 'use_custom', 'icon_size'] as $key )
|
||||
{
|
||||
unset( $values["custombadge_{$key}"] );
|
||||
unset( $values[ "{$prefix}_{$key}" ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Grouping
|
||||
* @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
|
||||
* @subpackage
|
||||
* @since 5/6/2025
|
||||
*/
|
||||
|
||||
namespace IPS\Node;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Db;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
trait Grouping
|
||||
{
|
||||
/**
|
||||
* Return all groups available for this node class
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function availableNodeGroups() : array
|
||||
{
|
||||
$return = [];
|
||||
$groups = NodeGroup::getStore()[ get_called_class() ] ?? [];
|
||||
if( !empty( $groups ) )
|
||||
{
|
||||
foreach( $groups as $group )
|
||||
{
|
||||
$return[ $group['group_id'] ] = NodeGroup::constructFromData( $group );
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
protected ?array $_nodeGroups = null;
|
||||
|
||||
/**
|
||||
* Return the groups to which this node belongs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function groups() : array
|
||||
{
|
||||
if( $this->_nodeGroups === null )
|
||||
{
|
||||
$this->_nodeGroups = iterator_to_array(
|
||||
new ActiveRecordIterator(
|
||||
Db::i()->select( 'core_node_groups.*', 'core_node_groups_nodes', [ 'core_node_groups_nodes.node_id=?', $this->_id ] )
|
||||
->join( 'core_node_groups', 'core_node_groups_nodes.group_id=core_node_groups.group_id' ),
|
||||
NodeGroup::class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->_nodeGroups;
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,9 @@ trait Icon
|
||||
'upload' => $prefix . 'icon_choose_upload'
|
||||
],
|
||||
'toggles' => [
|
||||
'icon' => [ $prefix . 'icon_picker' ], 'upload' => [ $prefix . 'icon_upload' ] ]
|
||||
] ) );
|
||||
'icon' => [ $prefix . 'icon_picker' ], 'upload' => [ $prefix . 'icon_upload' ] ],
|
||||
'disableCopy' => true
|
||||
], null, null, null, $prefix . 'icon_choose' ) );
|
||||
|
||||
$form->add( new Upload( $prefix . 'icon_upload', ( $this->icon_type == 'upload' ) ? File::get( static::iconStorageExtension(), $this->icon ) : NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => static::iconStorageExtension() ), NULL, NULL, NULL, $prefix . 'icon_upload' ) );
|
||||
|
||||
|
||||
+197
-10
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Group
|
||||
* @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
|
||||
* @subpackage
|
||||
* @since 5/5/2025
|
||||
*/
|
||||
|
||||
namespace IPS\Node;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Node;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use OutOfRangeException;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
class NodeGroup extends Model
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static array $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static ?string $databaseTable = 'core_node_groups';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Prefix
|
||||
*/
|
||||
public static string $databasePrefix = 'group_';
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static string $nodeTitle = 'node_groups';
|
||||
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}" as the key
|
||||
*/
|
||||
public static ?string $titleLangPrefix = 'core_node_group_';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Caches
|
||||
* @note Defined cache keys will be cleared automatically as needed
|
||||
*/
|
||||
protected array $caches = array( 'nodeGroups' );
|
||||
|
||||
/**
|
||||
* @brief Bitwise values for members_bitoptions field
|
||||
*/
|
||||
public static array $bitOptions = array(
|
||||
'bitoptions' => array(
|
||||
'bitoptions' => array(
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
*
|
||||
* @param Form $form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( Form &$form ) : void
|
||||
{
|
||||
$form->add( new Translatable( 'node_group_name', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? static::$titleLangPrefix . $this->id : NULL ) ) ) );
|
||||
|
||||
/* The node class should already be set at this point, but let's be sane */
|
||||
if( $this->class )
|
||||
{
|
||||
$field = new Node( 'node_group_nodes', iterator_to_array( $this->nodes() ), false, [
|
||||
'class' => $this->class,
|
||||
'multiple' => true
|
||||
] );
|
||||
|
||||
$nodeClass = $this->class;
|
||||
$field->label = Member::loggedIn()->language()->addToStack( $nodeClass::$nodeTitle );
|
||||
$form->add( $field );
|
||||
}
|
||||
|
||||
parent::form( $form );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Format form values from add/edit form for save
|
||||
*
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
if( !$this->id )
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $this->id, $values['node_group_name'] );
|
||||
unset( $values['node_group_name'] );
|
||||
|
||||
if( !empty( $values['node_group_nodes'] ) )
|
||||
{
|
||||
$current = iterator_to_array(
|
||||
Db::i()->select( 'node_id', 'core_node_groups_nodes', [ 'group_id=?', $this->id ] )
|
||||
);
|
||||
|
||||
$usedIds = [];
|
||||
foreach( $values['node_group_nodes'] as $node )
|
||||
{
|
||||
$usedIds[] = $node->_id;
|
||||
if( !in_array( $node->_id, $current ) )
|
||||
{
|
||||
Db::i()->insert( 'core_node_groups_nodes', [ 'group_id' => $this->id, 'node_id' => $node->id ] );
|
||||
}
|
||||
}
|
||||
|
||||
/* Anything to delete? */
|
||||
$toDelete = array_diff( $current, $usedIds );
|
||||
if( count( $toDelete ) )
|
||||
{
|
||||
Db::i()->delete( 'core_node_groups_nodes', [
|
||||
[ 'group_id=?', $this->id ],
|
||||
[ Db::i()->in( 'node_id', $toDelete ) ]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
unset( $values['node_group_nodes'] );
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all nodes that are part of this group
|
||||
*
|
||||
* @return ActiveRecordIterator
|
||||
*/
|
||||
public function nodes() : ActiveRecordIterator
|
||||
{
|
||||
$nodeClass = $this->class;
|
||||
return new ActiveRecordIterator(
|
||||
Db::i()->select( '*', $nodeClass::$databaseTable, Db::i()->in( $nodeClass::$databasePrefix . $nodeClass::$databaseColumnId, $this->nodeIds() ) ),
|
||||
$nodeClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Easier way to get the node IDs from the cache
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function nodeIds() : array
|
||||
{
|
||||
return static::getStore()[ $this->class ][ $this->id ]['nodes'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load cached data
|
||||
*
|
||||
* @note This should be overridden in your class if you enable $loadFromCache
|
||||
* @see ActiveRecord::$loadFromCache
|
||||
* @return array
|
||||
*/
|
||||
public static function getStore(): array
|
||||
{
|
||||
try
|
||||
{
|
||||
$cache = Store::i()->nodeGroups;
|
||||
}
|
||||
catch( OutOfRangeException )
|
||||
{
|
||||
$cache = [];
|
||||
foreach( Db::i()->select( '*', static::$databaseTable ) as $row )
|
||||
{
|
||||
if( !isset( $cache[ $row['group_class'] ] ) )
|
||||
{
|
||||
$cache[ $row['group_class'] ] = [];
|
||||
}
|
||||
|
||||
$row['nodes'] = iterator_to_array(
|
||||
Db::i()->select( 'node_id', 'core_node_groups_nodes', [ 'group_id=?', $row['group_id'] ] )
|
||||
);
|
||||
|
||||
$cache[ $row['group_class'] ][ $row['group_id'] ] = $row;
|
||||
}
|
||||
Store::i()->nodeGroups = $cache;
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() : void
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
Db::i()->delete( 'core_node_groups_nodes', [ 'group_id=?', $this->id ] );
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
namespace IPS\Node;
|
||||
|
||||
use IPS\Content\Permissions;
|
||||
use IPS\Member;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form;
|
||||
@@ -46,6 +47,12 @@ trait Ratings
|
||||
*/
|
||||
public function canRate( Member|null $member = NULL ): bool
|
||||
{
|
||||
/* Extensions go first */
|
||||
if( $permCheck = Permissions::can( 'rate', $this, $member ) )
|
||||
{
|
||||
return ( $permCheck === Permissions::PERM_ALLOW );
|
||||
}
|
||||
|
||||
$member = $member ?: Member::loggedIn();
|
||||
if( !$member->member_id )
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user