Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+17 -11
View File
@@ -11,29 +11,35 @@
namespace IPS\Node\Api\GraphQL;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Api\GraphQL\SafeException;
use IPS\Member;
use IPS\Node\Model;
use OutOfRangeException;
use function defined;
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;
}
/**
* @brief Base mutator class for nodes
*/
class _NodeMutator
class NodeMutator
{
/**
* Mark a node as read
*
* @param int $reactionID ID of reaction to add
* @param \IPS\Content\Comment $comment The comment to add a reaction on
* @return void
* @param Model $node The comment to add a reaction on
* @return Model
*/
protected function _markRead( $node )
protected function _markRead( Model $node ): Model
{
if( !\IPS\Member::loggedIn()->member_id )
if( !Member::loggedIn()->member_id )
{
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0004/1', 403 );
throw new SafeException( 'NOT_LOGGED_IN', 'GQL/0004/1', 403 );
}
try
@@ -41,9 +47,9 @@ class _NodeMutator
$itemClass = $node::$contentItemClass;
$itemClass::markContainerRead( $node );
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_NODE', 'GQL/0004/2', 403 );
throw new SafeException( 'INVALID_NODE', 'GQL/0004/2', 403 );
}
return $node;
+43 -24
View File
@@ -9,35 +9,44 @@
*/
namespace IPS\Node\Api\GraphQL;
use Exception;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\Fields\TagsEditField;
use IPS\Api\GraphQL\TypeRegistry;
use IPS\Content\Item;
use IPS\Member;
use IPS\Node\Model;
use IPS\Patterns\ActiveRecordIterator;
use function defined;
use function is_array;
/* 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' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Base class for Nodes
*/
class _NodeType extends ObjectType
class NodeType extends ObjectType
{
/*
/**
* @brief The item classname we use for this type
* @var Model|string
*/
protected static $nodeClass = '\IPS\Node\Model';
protected static string $nodeClass = '\IPS\Node\Model';
/*
* @brief GraphQL type name
*/
protected static $typeName = 'core_Node';
protected static string $typeName = 'core_Node';
/*
* @brief GraphQL type description
*/
protected static $typeDescription = 'A generic node';
protected static string $typeDescription = 'A generic node';
public function __construct()
@@ -58,7 +67,7 @@ class _NodeType extends ObjectType
*
* @return array
*/
public function fields()
public function fields(): array
{
return array(
'id' => [
@@ -70,7 +79,7 @@ class _NodeType extends ObjectType
'name' => [
'type' => TypeRegistry::string(),
'resolve' => function ($node) {
return $node->getTitleForLanguage( \IPS\Member::loggedIn()->language() );
return $node->getTitleForLanguage( Member::loggedIn()->language() );
}
],
'url' => [
@@ -82,12 +91,14 @@ class _NodeType extends ObjectType
'itemCount' => [
'type' => TypeRegistry::int(),
'resolve' => function ($node) {
/* @var Item $contentItemClass */
return static::$nodeClass::$contentItemClass::contentCount($node, TRUE, FALSE);
}
],
'commentCount' => [
'type' => TypeRegistry::int(),
'resolve' => function ($node) {
/* @var Item $contentItemClass */
return static::$nodeClass::$contentItemClass::contentCount($node, FALSE, TRUE);
}
],
@@ -100,6 +111,7 @@ class _NodeType extends ObjectType
'hasUnread' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($node) {
/* @var Item $contentItemClass */
return static::$nodeClass::$contentItemClass::containerUnread( $node );
}
],
@@ -113,7 +125,7 @@ class _NodeType extends ObjectType
'follow' => [
'type' => TypeRegistry::follow(),
'resolve' => function ($node) {
if( isset( static::$followData ) && \is_array( static::$followData ) ){
if( isset( static::$followData ) && is_array( static::$followData ) ){
return array_merge( static::$followData, array(
'id' => $node->_id,
'node' => $node,
@@ -142,7 +154,7 @@ class _NodeType extends ObjectType
return array( 'postKey' => $args['postKey'] );
}
],
'tagPermissions' => \IPS\Api\GraphQL\Fields\TagsEditField::getDefinition(static::$typeName . '_tags')
'tagPermissions' => TagsEditField::getDefinition(static::$typeName . '_tags')
);
}
@@ -151,7 +163,7 @@ class _NodeType extends ObjectType
*
* @return ObjectType
*/
public static function getItemType()
public static function getItemType(): ObjectType
{
return \IPS\Content\Api\GraphQL\TypeRegistry::item();
}
@@ -161,19 +173,20 @@ class _NodeType extends ObjectType
*
* @return array
*/
public static function getNodePermissionFields()
public static function getNodePermissionFields(): array
{
return array(
'canCreate' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($node, $args, $context) {
return $node->can('add', \IPS\Member::loggedIn(), FALSE);
return $node->can('add', Member::loggedIn(), FALSE);
}
],
'itemsRequireApproval' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($node, $args, $context) {
return static::$nodeClass::$contentItemClass::moderateNewItems( \IPS\Member::loggedIn(), $node, FALSE );
/* @var Item $contentItemClass */
return static::$nodeClass::$contentItemClass::moderateNewItems( Member::loggedIn(), $node, FALSE );
}
],
'commentsRequireApproval' => [
@@ -196,10 +209,12 @@ class _NodeType extends ObjectType
/**
* Resolve children field
*
* @param \IPS\Node\Model
* @return array
* @param Model $node
* @param array $args
* @param array $context
* @return array
*/
protected static function children($node, $args, $context)
protected static function children( Model $node, array $args, array $context): array
{
return $node->children('view');
}
@@ -207,11 +222,11 @@ class _NodeType extends ObjectType
/**
* Resolve the topics field
*
* @param \IPS\forums\Forum
* @param array Arguments passed to this resolver
* @return array
* @param Model $node
* @param array $args Arguments passed to this resolver
* @return ActiveRecordIterator|int
*/
protected static function items($node, $args)
protected static function items( Model $node, array $args): ActiveRecordIterator|int
{
try
{
@@ -221,20 +236,24 @@ class _NodeType extends ObjectType
}
else
{
/* @var Item $contentItemClass */
/* @var array $databaseColumnMap */
$orderBy = static::$nodeClass::$contentItemClass::$databaseColumnMap[ $args['orderBy'] ];
}
if( $args['orderBy'] === 'last_comment' )
{
$orderBy = \is_array( $orderBy ) ? array_pop( $orderBy ) : $orderBy;
$orderBy = is_array( $orderBy ) ? array_pop( $orderBy ) : $orderBy;
}
}
catch (\Exception $e)
catch (Exception $e)
{
$orderBy = 'title';
}
$where = array();
/* @var Item $class */
$class = static::$nodeClass::$contentItemClass;
$sortBy = $class::$databaseTable . '.' . $class::$databasePrefix . "{$orderBy} {$args['orderDir']}";
+8 -8
View File
@@ -10,21 +10,21 @@
*/
namespace IPS\Node\Api\GraphQL;
use GraphQL\Type\Definition\Type;
use function defined;
/* 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' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* \IPS\Node base types
*/
class _TypeRegistry
class TypeRegistry
{
protected static $node;
protected static ?NodeType $node = NULL;
/**
* Constructor
@@ -35,10 +35,10 @@ class _TypeRegistry
}
/**
* @return CommentType
* @return NodeType
*/
public static function node()
public static function node() : NodeType
{
return self::$node ?: (self::$node = new \IPS\Node\Api\GraphQL\NodeType());
return self::$node ?: (self::$node = new NodeType());
}
}
+54 -39
View File
@@ -11,30 +11,46 @@
namespace IPS\Node\Api;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Api\Controller;
use IPS\Api\Exception;
use IPS\Api\PaginatedResponse;
use IPS\Api\Response;
use IPS\Db;
use IPS\IPS;
use IPS\Node\Model;
use IPS\Node\Permissions;
use IPS\Request;
use OutOfRangeException;
use function defined;
use function in_array;
use function is_array;
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;
}
/**
* @brief Base API endpoint for Nodes
*/
class _NodeController extends \IPS\Api\Controller
class NodeController extends Controller
{
/**
* List
*
* @param array $where Extra WHERE clause
* @return \IPS\Api\PaginatedResponse
* @return PaginatedResponse
*/
protected function _list( $where = array() )
protected function _list( array $where = array() ) : PaginatedResponse
{
$class = $this->class;
if ( $this->member and \in_array( 'IPS\Node\Permissions', class_implements( $class ) ) )
/* @var array $permissionMap */
if ( $this->member and in_array( 'IPS\Node\Permissions', class_implements( $class ) ) )
{
$where[] = array( '(' . \IPS\Db::i()->findInSet( 'core_permission_index.perm_' . $class::$permissionMap['view'], $this->member->permissionArray() ) . ' OR ' . 'core_permission_index.perm_' . $class::$permissionMap['view'] . '=? )', '*' );
$where[] = array( '(' . Db::i()->findInSet( 'core_permission_index.perm_' . $class::$permissionMap['view'], $this->member->permissionArray() ) . ' OR ' . 'core_permission_index.perm_' . $class::$permissionMap['view'] . '=? )', '*' );
if ( $class::$databaseColumnEnabledDisabled )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnEnabledDisabled . '=1' );
@@ -42,28 +58,28 @@ class _NodeController extends \IPS\Api\Controller
}
/* Exclude clubs? */
if ( \IPS\IPS::classUsesTrait( $class, 'IPS\Content\ClubContainer' ) AND isset( \IPS\Request::i()->clubs ) AND !\IPS\Request::i()->clubs )
if ( IPS::classUsesTrait( $class, 'IPS\Content\ClubContainer' ) AND isset( Request::i()->clubs ) AND !Request::i()->clubs )
{
$where[] = array( $class::$databasePrefix . $class::clubIdColumn() . ' IS NULL' );
}
$select = \IPS\Db::i()->select( '*', $class::$databaseTable, $where, $class::$databaseColumnOrder ? $class::$databasePrefix . $class::$databaseColumnOrder . " asc" : NULL );
$select = Db::i()->select( '*', $class::$databaseTable, $where, $class::$databaseColumnOrder ? $class::$databasePrefix . $class::$databaseColumnOrder . " asc" : NULL );
/* Return permissions */
if ( \in_array( 'IPS\Node\Permissions', class_implements( $class ) ) )
if ( in_array( 'IPS\Node\Permissions', class_implements( $class ) ) )
{
$select->join( 'core_permission_index', array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $class::$databaseTable . "." . $class::$databasePrefix . $class::$databaseColumnId, $class::$permApp, $class::$permType ) );
}
/* Return */
return new \IPS\Api\PaginatedResponse(
return new PaginatedResponse(
200,
$select,
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
isset( Request::i()->page ) ? Request::i()->page : 1,
$class,
NULL,
$this->member,
isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : NULL
isset( Request::i()->perPage ) ? Request::i()->perPage : NULL
);
}
@@ -71,19 +87,19 @@ class _NodeController extends \IPS\Api\Controller
* View
*
* @param int $id ID Number
* @return \IPS\Api\Response
* @return Response
*/
protected function _view( $id )
protected function _view( int $id ) : Response
{
$class = $this->class;
$node = $class::load( $id );
if ( $this->member and !$node->can( 'view', $this->member ) )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
return new \IPS\Api\Response( 200, $node->apiOutput( $this->member ) );
return new Response( 200, $node->apiOutput( $this->member ) );
}
/**
@@ -94,9 +110,9 @@ class _NodeController extends \IPS\Api\Controller
* @throws 1S359/1 INVALID_ID The node ID does not exist
* @throws 1S359/2 INVALID_TARGET The target node cannot be deleted because the new parent node does not exist
* @throws 1S359/3 HAS_CHILDREN The target node cannot be deleted because it has children (pass deleteChildrenOrMove in the request to specify how to handle the children)
* @return \IPS\Api\Response
* @return Response
*/
protected function _delete( $id, $deleteChildrenOrMove = NULL )
protected function _delete( int $id, ?int $deleteChildrenOrMove = NULL ) : Response
{
$class = $this->class;
@@ -118,15 +134,15 @@ class _NodeController extends \IPS\Api\Controller
$target = $class::load( $deleteChildrenOrMove );
$node->deleteOrMoveFormSubmit( array( 'node_move_children' => TRUE, 'node_destination' => $target->_id, 'node_move_content' => $target ) );
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_TARGET', '1S359/2', 404 );
throw new Exception( 'INVALID_TARGET', '1S359/2', 404 );
}
}
/* Or return an exception if no action was set */
if ( !$deleteChildrenOrMove )
{
throw new \IPS\Api\Exception( 'HAS_CHILDREN', '1S359/3', 404 );
throw new Exception( 'HAS_CHILDREN', '1S359/3', 404 );
}
}
else
@@ -134,25 +150,25 @@ class _NodeController extends \IPS\Api\Controller
$node->delete();
}
return new \IPS\Api\Response( 200, NULL );
return new Response( 200, NULL );
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '1S359/1', 404 );
throw new Exception( 'INVALID_ID', '1S359/1', 404 );
}
}
/**
* Create or update node
*
* @param \IPS\node\Model $node The node
* @return \IPS\node\Model
* @param Model $node The node
* @return Model
*/
protected function _createOrUpdate( \IPS\Node\Model $node )
protected function _createOrUpdate( Model $node ): Model
{
$node->save();
if( $node instanceof \IPS\Node\Permissions AND isset( \IPS\Request::i()->permissions ) AND \IPS\Request::i()->permissions )
if( $node instanceof Permissions AND isset( Request::i()->permissions ) AND Request::i()->permissions )
{
$insert = array(
'app' => $node::$permApp,
@@ -162,9 +178,9 @@ class _NodeController extends \IPS\Api\Controller
foreach( $node::$permissionMap as $key => $field )
{
if( isset( \IPS\Request::i()->permissions[ $key ] ) )
if( isset( Request::i()->permissions[ $key ] ) )
{
$insert[ 'perm_' . $field ] = \is_array( \IPS\Request::i()->permissions[ $key ] ) ? implode( ',', \IPS\Request::i()->permissions[ $key ] ) : \IPS\Request::i()->permissions[ $key ];
$insert[ 'perm_' . $field ] = is_array( Request::i()->permissions[ $key ] ) ? implode( ',', Request::i()->permissions[ $key ] ) : Request::i()->permissions[ $key ];
}
}
@@ -178,9 +194,9 @@ class _NodeController extends \IPS\Api\Controller
/**
* Create
*
* @return \IPS\Content\Node
* @return Model
*/
protected function _create()
protected function _create() : Model
{
$class = $this->class;
@@ -190,14 +206,13 @@ class _NodeController extends \IPS\Api\Controller
if( isset( $node::$databaseColumnOrder ) AND $node::$automaticPositionDetermination === TRUE )
{
$orderColumn = $node::$databaseColumnOrder;
$node->$orderColumn = \IPS\Db::i()->select( 'MAX(' . $node::$databasePrefix . $orderColumn . ')', $node::$databaseTable )->first() + 1;
$node->$orderColumn = Db::i()->select( 'MAX(' . $node::$databasePrefix . $orderColumn . ')', $node::$databaseTable )->first() + 1;
}
$node->save();
$node = $this->_createOrUpdate( $node );
/* Output */
return $node;
return $this->_createOrUpdate( $node );
}
/**
@@ -213,9 +228,9 @@ class _NodeController extends \IPS\Api\Controller
$where = $where ?: [];
$idField = $class::$databaseTable . '.' . $class::$databasePrefix . '.' . $class::$databaseColumnId;
if ( isset( \IPS\Request::i()->ids ) )
if ( isset( Request::i()->ids ) )
{
$where[] = array( \IPS\Db::i()->in( $idField, array_map( 'intval', explode(',', \IPS\Request::i()->ids ) ) ) );
$where[] = array( Db::i()->in( $idField, array_map( 'intval', explode(',', Request::i()->ids ) ) ) );
}
return $where;
+42 -24
View File
@@ -11,9 +11,15 @@
namespace IPS\Node;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Member;
use IPS\Theme;
use function defined;
use function substr;
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;
}
@@ -25,24 +31,24 @@ trait Colorize
/**
* @brief Table column name that holds the feature color hex
*/
public static $featureColumnName = 'feature_color';
public static string $featureColumnName = 'feature_color';
/**
* @brief Cache the calculated formatted title for this node
*/
protected $formattedTitle = NULL;
protected ?string $formattedTitle = NULL;
/**
* @brief Cache the formatted text colour for this node
*/
protected $featureTextColor = NULL;
protected ?string $featureTextColor = NULL;
/**
* Get HTML formatted title. Allows apps or nodes to format the title, such as adding different colours, etc
*
* @return string
*/
public function get__formattedTitle()
public function get__formattedTitle(): string
{
if ( $this->formattedTitle === NULL )
{
@@ -54,19 +60,30 @@ trait Colorize
}
else
{
$this->formattedTitle = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->formattedTitle( $this );
$this->formattedTitle = Theme::i()->getTemplate( 'global', 'core', 'front' )->formattedTitle( $this );
}
}
return $this->formattedTitle;
}
/**
* Get the featured color for use in CSS
*
* @return string|null
*/
public function get__featureColor(): ?string
{
$columnName = static::$featureColumnName;
return $this->$columnName;
}
/**
* Get the featured text color for use in CSS
*
* @return string
* @return string|null
*/
public function get__featureTextColor()
public function get__featureTextColor(): ?string
{
if ( $this->featureTextColor === NULL )
{
@@ -83,26 +100,27 @@ trait Colorize
return $this->featureTextColor;
}
/**
* Static method to fetch the text colour based on the the contrast from the background color
*
* @return string
* @param $featureColor
* @return string
*/
public static function featureTextColor( $featureColor )
public static function featureTextColor( $featureColor ): string
{
$hexColor = str_replace( '#', '', $featureColor );
$blackColor = '000000';
$textColor = '#ffffff';
/* Use luminosity contrast algorithm to determine font color */
$r1 = hexdec( \substr( $hexColor, 0, 2 ) );
$g1 = hexdec( \substr( $hexColor, 2, 2 ) );
$b1 = hexdec( \substr( $hexColor, 4, 2 ) );
$r1 = hexdec( substr( $hexColor, 0, 2 ) );
$g1 = hexdec( substr( $hexColor, 2, 2 ) );
$b1 = hexdec( substr( $hexColor, 4, 2 ) );
$r2 = hexdec( \substr( $blackColor, 0, 2 ) );
$g2 = hexdec( \substr( $blackColor, 2, 2 ) );
$b2 = hexdec( \substr( $blackColor, 4, 2 ) );
$r2 = hexdec( substr( $blackColor, 0, 2 ) );
$g2 = hexdec( substr( $blackColor, 2, 2 ) );
$b2 = hexdec( substr( $blackColor, 4, 2 ) );
$l1 = 0.2126 * pow( $r1 / 255, 2.2 ) + 0.7152 * pow( $g1 / 255, 2.2 ) + 0.0722 * pow( $b1 / 255, 2.2 );
$l2 = 0.2126 * pow( $r2 / 255, 2.2 ) + 0.7152 * pow( $g2 / 255, 2.2 ) + 0.0722 * pow( $b2 / 255, 2.2 );
@@ -133,9 +151,9 @@ trait Colorize
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param bool $escape If the title should be escaped for HTML output. If FALSE, the feature color will not be used, because that requires HTML output
* @return \IPS\Http\Url
* @return mixed
*/
public static function titleFromIndexData( $indexData, $itemData, $containerData, $escape = TRUE )
public static function titleFromIndexData(array $indexData, array $itemData, ?array $containerData, bool $escape = TRUE ): mixed
{
if ( !$escape or !isset( $containerData[ static::$databasePrefix . static::$featureColumnName ] ) )
{
@@ -143,17 +161,17 @@ trait Colorize
}
$node = $containerData;
$node['title'] = \IPS\Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $indexData['index_container_id'], 'NULL', array( 'escape' => true ) );
$node['title'] = Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $indexData['index_container_id'], 'NULL', array( 'escape' => true ) );
$node['text_color'] = static::featureTextColor( $node[ static::$databasePrefix . static::$featureColumnName ] );
/* Normalize */
$node['feature_color'] = $node[ static::$databasePrefix . static::$featureColumnName ];
$title = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->formattedTitle( $node );
$title = Theme::i()->getTemplate( 'global', 'core', 'front' )->formattedTitle( $node );
if ( $indexData['index_club_id'] and isset( $containerData['_club'] ) )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'club_container_title', FALSE, array( 'sprintf' => array( $containerData['_club']['name'] ), 'htmlsprintf' => array( $title ) ) );
return Member::loggedIn()->language()->addToStack( 'club_container_title', FALSE, array( 'sprintf' => array( $containerData['_club']['name'] ), 'htmlsprintf' => array( $title ) ) );
}
else
{
+354 -327
View File
File diff suppressed because it is too large. Load diff
+12 -12
View File
@@ -7,16 +7,19 @@
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage
* @since 6/25/2024
* @since 7/23/2024
*/
namespace IPS\Node;
use BadMethodCallException;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Data\Store;
use IPS\Redis;
use OutOfRangeException;
use BadMethodCallException;
use function get_called_class;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
@@ -30,7 +33,7 @@ trait DelayedCount
*/
protected function getStorageId() : string
{
return \get_called_class() . '_' . $this->_id;
return get_called_class() . '_' . $this->_id;
}
/**
@@ -71,7 +74,7 @@ trait DelayedCount
Redis::i()->expire( 'nodeSyncTimes', ( 2 * 3600 ) );
}
}
catch( BadMethodCallException | \RedisException )
catch( BadMethodCallException )
{
try
{
@@ -93,10 +96,7 @@ trait DelayedCount
/* Bubble up to parent nodes */
try
{
if( $parent = $this->parent() )
{
$parent->storeUpdateTime();
}
$this->parent()?->storeUpdateTime();
}
catch( \OutOfRangeException )
{}
@@ -156,7 +156,7 @@ trait DelayedCount
/**
* Count all comments, items, etc
*
* @return mixed
* @return void
*/
abstract protected function recount();
}
abstract protected function recount() : void;
}
+177
View File
@@ -0,0 +1,177 @@
<?php
/**
* @brief Icon
* @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 3/26/2024
*/
namespace IPS\Node;
/* To prevent PHP errors (extending class does not exist) revealing path */
use ErrorException;
use Exception;
use IPS\File;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Icon as FormIcon;
use IPS\Helpers\Form\Radio;
use IPS\Helpers\Form\Upload;
use IPS\Theme;
use function is_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
trait Icon
{
/**
* @brief The column that holds the icon data, without the prefix
*/
public static string $iconColumn = 'icon';
/**
* @return string
*/
public function get_icon_type() : string
{
$iconColumn = static::$iconColumn;
if ( $this->$iconColumn and $iconData = json_decode( $this->$iconColumn, TRUE ) )
{
return 'icon';
}
return ( $this->$iconColumn ) ? 'upload' : 'none';
}
/**
* @return string
*/
public static function iconFormPrefix() : string
{
return static::$iconFormPrefix ?? '';
}
/**
* @return string
*/
public static function iconStorageExtension() : string
{
return static::$iconStorageExtension ?? '';
}
/**
* Add icon fields to the node form
*
* @param Form $form
* @return void
*/
public function iconFormFields( Form &$form ) : void
{
$prefix = static::iconFormPrefix();
$form->add( new Radio( $prefix . 'icon_choose', $this->icon_type, null, [
'options' => [
'none' => $prefix . 'icon_choose_icon_none',
'icon' => $prefix . 'icon_choose_icon',
'upload' => $prefix . 'icon_choose_upload'
],
'toggles' => [
'icon' => [ $prefix . 'icon_picker' ], 'upload' => [ $prefix . 'icon_upload' ] ]
] ) );
$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' ) );
$form->add( new FormIcon( $prefix . 'icon_picker', ( $this->icon_type == 'icon' ? json_decode( $this->icon, true ) : null ), FALSE, array(), NULL, NULL, NULL, $prefix . 'icon_picker' ) );
}
/**
* Handle the above icon fields
*
* @param array $values
* @return array
*/
public function formatIconFieldValues( array $values ) : array
{
/* Icon? Me? No. */
$iconValue = null;
$prefix = static::iconFormPrefix();
if ( isset( $values[ $prefix . 'icon_choose' ] ) )
{
switch ( $values[ $prefix . 'icon_choose' ] )
{
case 'upload':
if ( $values[ $prefix . 'icon_upload'] )
{
$iconValue = (string) $values[ $prefix . 'icon_upload'];
}
break;
case 'icon':
if ( is_array( $values[ $prefix . 'icon_picker'] ) )
{
$iconValue = json_encode( $values[ $prefix . 'icon_picker'] );
}
break;
}
}
$values[ $prefix . static::$iconColumn ] = $iconValue;
unset( $values[ $prefix . 'icon_choose' ], $values[ $prefix . 'icon_upload' ], $values[ $prefix . 'icon_picker' ] );
return $values;
}
/**
* If the icon was uploaded, return the file
*
* @return File|null
*/
public function getIconFile() : ?File
{
$iconColumn = static::$iconColumn;
if( $this->icon_type == 'upload' )
{
try
{
return File::get( static::iconStorageExtension(), $this->$iconColumn );
}
catch( Exception $e ){}
}
return null;
}
/**
* Return icon as a string
*
* @return string
* @throws ErrorException
*/
public function getIcon(): string
{
$iconColumn = static::$iconColumn;
if ( $iconData = json_decode( $this->$iconColumn, true ) )
{
$iconData = $iconData[0];
if ( isset( $iconData['html'] ) )
{
return $iconData['html'];
}
}
else if ( ! empty( $this->$iconColumn ) and $iconFile = $this->getIconFile() )
{
/* Must be an upload */
return Theme::i()->getTemplate( 'global', 'core', 'front' )->uploadedIcon( $iconFile );
}
return '';
}
}
+904 -727
View File
File diff suppressed because it is too large. Load diff
+5 -2
View File
@@ -11,9 +11,12 @@
namespace IPS\Node;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
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;
}
+128 -7
View File
@@ -1,6 +1,6 @@
<?php
/**
* @brief Rating Interface for Nodes
* @brief Rating Trait for Nodes
* @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/
@@ -10,18 +10,139 @@
namespace IPS\Node;
use IPS\Member;
use IPS\Db;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Rating;
use IPS\Request;
use IPS\Output;
use IPS\Theme;
use UnderflowException;
use function defined;
use function header;
use function get_called_class;
use function round;
use function time;
/* 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' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Rating Interface for Nodes
*
* @note Node classes will gain special functionality by implementing this interface
* Rating Trait for Nodes
*/
interface Ratings
trait Ratings
{
/**
* Can Rate?
*
* @param Member|NULL $member The member to check for (NULL for currently logged in member)
* @return bool
*/
public function canRate( Member|null $member = NULL ): bool
{
$member = $member ?: Member::loggedIn();
if( !$member->member_id )
{
return false;
}
try
{
$idColumn = static::$databaseColumnId;
Db::i()->select( '*', 'core_ratings', array( 'class=? AND item_id=? AND `member`=?', get_called_class(), $this->$idColumn, $member->member_id ) )->first();
return FALSE;
}
catch ( UnderflowException $e )
{
return TRUE;
}
}
/**
* Get average rating
*
* @return int
*/
public function averageRating(): int
{
if ( isset( static::$ratingColumnMap['rating_average'] ) )
{
$column = static::$ratingColumnMap['rating_average'];
return $this->$column;
}
elseif ( isset( static::$ratingColumnMap['rating_total'] ) and isset( static::$ratingColumnMap['rating_hits'] ) )
{
$hits = static::$ratingColumnMap['rating_hits'];
$total = static::$ratingColumnMap['rating_total'];
return $this->$hits ? round( $this->$total / $this->$hits, 1 ) : 0;
}
else
{
$idColumn = static::$databaseColumnId;
return round( Db::i()->select( 'AVG(rating)', 'core_ratings', array( 'class=? AND item_id=?', get_called_class(), $this->$idColumn ) )->first(), 1 );
}
}
/**
* Display rating (will just display stars if member cannot rate)
*
* @return string
*/
public function rating(): string
{
if ( $this->canRate() )
{
$idColumn = static::$databaseColumnId;
$form = new Form('rating');
$form->add( new Rating( 'rating', $this->averageRating() ) );
if ( $values = $form->values() )
{
Db::i()->insert( 'core_ratings', array(
'class' => get_called_class(),
'item_id' => $this->$idColumn,
'member' => Member::loggedIn()->member_id,
'rating' => $values['rating'],
'ip' => Request::i()->ipAddress(),
'rating_date' => time()
), TRUE );
if ( isset( static::$ratingColumnMap['rating_average'] ) )
{
$column = static::$ratingColumnMap['rating_average'];
$this->$column = round( Db::i()->select( 'AVG(rating)', 'core_ratings', array( 'class=? AND item_id=?', get_called_class(), $this->$idColumn ) )->first(), 1 );
}
if ( isset( static::$ratingColumnMap['rating_total'] ) )
{
$column = static::$ratingColumnMap['rating_total'];
$this->$column = Db::i()->select( 'SUM(rating)', 'core_ratings', array( 'class=? AND item_id=?', get_called_class(), $this->$idColumn ) )->first();
}
if ( isset( static::$ratingColumnMap['rating_hits'] ) )
{
$column = static::$ratingColumnMap['rating_hits'];
$this->$column = Db::i()->select( 'COUNT(*)', 'core_ratings', array( 'class=? AND item_id=?', get_called_class(), $this->$idColumn ) )->first();
}
$this->save();
if ( Request::i()->isAjax() )
{
Output::i()->json( 'OK' );
}
}
return $form->customTemplate( array( Theme::i()->getTemplate( 'forms', 'core' ), 'ratingTemplate' ) );
}
else
{
return Theme::i()->getTemplate( 'global', 'core' )->rating( 'veryLarge', $this->averageRating() );
}
}
}
+72 -68
View File
@@ -13,10 +13,17 @@ namespace IPS\Node;
/* To prevent PHP errors (extending class does not exist) revealing path */
use Exception;
use IPS\Content\Comment;
use IPS\Content\Item;
use IPS\Db;
use IPS\Member;
use function count;
use function defined;
use function is_array;
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;
}
@@ -28,7 +35,7 @@ trait Statistics
/**
* @brief Cached array of IDs a member has posted in
*/
protected $authorsPostedIn = array();
protected array $authorsPostedIn = array();
/**
* Get authors posted in
@@ -36,7 +43,7 @@ trait Statistics
* @param array $inSet The item_ids to check
* @return array
*/
public function authorsPostedIn( array $inSet )
public function authorsPostedIn( array $inSet ): array
{
if ( !isset( static::$contentItemClass ) )
{
@@ -59,12 +66,12 @@ trait Statistics
return array();
}
$items = iterator_to_array( \IPS\Db::i()->select( 'map_item_id, map_member_id', 'core_item_member_map', array( array( 'map_class=?', $contentItemClass ), array( \IPS\Db::i()->in( 'map_item_id', $inSet ) ) ) ) );
$items = iterator_to_array( Db::i()->select( 'map_item_id, map_member_id', 'core_item_member_map', array( array( 'map_class=?', $contentItemClass ), array( Db::i()->in( 'map_item_id', $inSet ) ) ) ) );
$reload = FALSE;
$itemIds = array();
/* Do we have the items we need? */
if ( \count( $items ) )
if ( count( $items ) )
{
foreach ( $items as $row )
{
@@ -73,7 +80,7 @@ trait Statistics
$diff = array_diff( $inSet, $itemIds );
if ( \count( $diff ) )
if ( count( $diff ) )
{
$reload = TRUE;
$this->rebuildPostedIn( $diff );
@@ -89,10 +96,10 @@ trait Statistics
/* Do we need to reload? */
if ( $reload )
{
$items = iterator_to_array( \IPS\Db::i()->select( 'map_item_id, map_member_id', 'core_item_member_map', array(array('map_class=?', $contentItemClass), array(\IPS\Db::i()->in( 'map_item_id', $inSet ) ) ) ) );
$items = iterator_to_array( Db::i()->select( 'map_item_id, map_member_id', 'core_item_member_map', array(array('map_class=?', $contentItemClass), array(Db::i()->in( 'map_item_id', $inSet ) ) ) ) );
$itemIds = [];
if ( \count( $items ) )
if ( count( $items ) )
{
foreach ( $items as $row )
{
@@ -105,11 +112,11 @@ trait Statistics
/* Check to see if there are any item IDs that can't be set because of missing posts, etc. This prevents an attempted rebuild from occuring over and over again */
$diff = array_diff( $inSet, $itemIds );
if ( \count( $diff ) )
if ( count( $diff ) )
{
foreach( $diff as $id )
{
\IPS\Db::i()->replace( 'core_item_member_map', array( 'map_class' => $contentItemClass, 'map_item_id' => $id, 'map_member_id' => NULL ), TRUE );
Db::i()->replace( 'core_item_member_map', array( 'map_class' => $contentItemClass, 'map_item_id' => $id, 'map_member_id' => NULL ), TRUE );
}
}
}
@@ -129,22 +136,22 @@ trait Statistics
/**
* @brief Cached array of IDs a member has posted in (calculated the expensive way so cached separately)
*/
protected $contentPostedIn = array();
protected array $contentPostedIn = array();
/**
* Retrieve an array of IDs a member has posted in.
*
* @param \IPS\Member|NULL $member The member (NULL for currently logged in member)
* @param array|NULL $inSet If supplied, checks will be restricted to only the ids provided
* @param array|NULL $additionalWhere Additional where clause
* @param array|NULL $commentJoinWhere Additional join clause for comments table
* @param Member|null $member The member (NULL for currently logged in member)
* @param array|null $inSet If supplied, checks will be restricted to only the ids provided
* @param array|null $additionalWhere Additional where clause
* @param array|null $commentJoinWhere Additional join clause for comments table
* @return array An array of content item ids
*/
public function contentPostedIn( $member=NULL, $inSet=NULL, $additionalWhere=NULL, $commentJoinWhere=NULL )
public function contentPostedIn(Member $member=NULL, array $inSet=NULL, array $additionalWhere=NULL, array $commentJoinWhere=NULL ): array
{
if ( $member === NULL )
{
$member = \IPS\Member::loggedIn();
$member = Member::loggedIn();
}
if( !$member->member_id )
@@ -157,7 +164,7 @@ trait Statistics
return array();
}
if ( \is_array( $inSet ) AND $additionalWhere === NULL AND $commentJoinWhere === NULL )
if ( is_array( $inSet ) AND $additionalWhere === NULL AND $commentJoinWhere === NULL )
{
/* Hooray, the easy efficient way */
$members = $this->authorsPostedIn( $inSet );
@@ -172,7 +179,10 @@ trait Statistics
else
{
/* The complicated way, which doesn't happen that often */
/* @var Item $contentItemClass */
$contentItemClass = static::$contentItemClass;
/* @var Comment $commentClass */
$commentClass = $contentItemClass::$commentClass;
$idColumn = static::$databaseColumnId;
$_key = md5( $member->member_id . json_encode( $inSet ) );
@@ -184,6 +194,7 @@ trait Statistics
$where = array();
/* @var array $databaseColumnMap */
if ( $contentItemClass::$firstCommentRequired )
{
$where[] = array( '(' . $commentClass::$databaseTable . '.' . $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] . '=? )', $member->member_id );
@@ -194,7 +205,7 @@ trait Statistics
$where[] = array( '(' . $commentClass::$databaseTable . '.' . $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] . '=? OR ' . $contentItemClass::$databaseTable . '.' . $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnMap['author'] . '=?)', $member->member_id, $member->member_id );
}
if( \is_array( $inSet ) AND \count( $inSet ) )
if( is_array( $inSet ) AND count( $inSet ) )
{
$where[] = array( $contentItemClass::$databaseTable . '.' . $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnId . ' IN(' . implode( ',', $inSet ) . ')' );
}
@@ -206,7 +217,7 @@ trait Statistics
$joinClause = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=' . $contentItemClass::$databaseTable . '.' . $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnId );
$items = \IPS\Db::i()->select( $contentItemClass::$databaseTable . '.' . $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnId, $contentItemClass::$databaseTable, $where, NULL, NULL, NULL, NULL, \IPS\Db::SELECT_DISTINCT );
$items = Db::i()->select( $contentItemClass::$databaseTable . '.' . $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnId, $contentItemClass::$databaseTable, $where, NULL, NULL, NULL, NULL, Db::SELECT_DISTINCT );
$items->join( $commentClass::$databaseTable, ( $commentJoinWhere !== NULL ) ? array( $joinClause, $commentJoinWhere ) : $joinClause );
$ids = array();
@@ -227,30 +238,32 @@ trait Statistics
* Note that we do fetch guests content here. That is to prevent content from being rebuilt on each click if it solely populated by guests
*
* @param array $inSet Array of item IDs
* @param array $members Optional array of member objects to limit by
* @param array $members Optional array of member objects to limit by\
* @return void
*/
public function rebuildPostedIn( array $inSet, $members=array() )
public function rebuildPostedIn( array $inSet, array $members=array() ) : void
{
/* @var array $databaseColumnMap */
$contentItemClass = static::$contentItemClass;
$commentClass = $contentItemClass::$commentClass;
$commentItemField = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'];
$authorColumn = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'];
$where = array();
$memberIds = array();
$deleteWhere = array( array( \IPS\Db::i()->in( 'map_item_id', $inSet ) ) );
if ( \count( $members ) )
$deleteWhere = array( array( Db::i()->in( 'map_item_id', $inSet ) ) );
if ( count( $members ) )
{
$memberIds = array();
foreach ( $members as $member )
{
$memberIds[] = $member->member_id;
}
$where[] = array(\IPS\Db::i()->in( $authorColumn, $memberIds ));
$deleteWhere[] = array( \IPS\Db::i()->in( 'map_member_id', $memberIds ) );
$where[] = array(Db::i()->in( $authorColumn, $memberIds ));
$deleteWhere[] = array( Db::i()->in( 'map_member_id', $memberIds ) );
}
\IPS\Db::i()->delete( 'core_item_member_map', $deleteWhere );
Db::i()->delete( 'core_item_member_map', $deleteWhere );
/* Do the item first if the first comment is not required */
if ( ! $contentItemClass::$firstCommentRequired )
@@ -261,18 +274,38 @@ trait Statistics
if ( isset( $contentItemClass::$databaseColumnMap['approved'] ) )
{
$approvedColumn = $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnMap['approved'];
$itemWhere[] = array( \IPS\Db::i()->in( $approvedColumn, array( 1, 2 ) ) ); # We want approved comments but also comments hidden because the item is hidden
$itemWhere[] = array( Db::i()->in( $approvedColumn, array( 1, 2 ) ) ); # We want approved comments but also comments hidden because the item is hidden
}
if ( isset( $contentItemClass::$databaseColumnMap['hidden'] ) )
{
$hiddenColumn = $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnMap['hidden'];
$itemWhere[] = array( \IPS\Db::i()->in( $hiddenColumn, array( 0, 2 ) ) ); # We want approved comments but also comments hidden because the item is hidden
$itemWhere[] = array( Db::i()->in( $hiddenColumn, array( 0, 2 ) ) ); # We want approved comments but also comments hidden because the item is hidden
}
$itemWhere[] = array( \IPS\Db::i()->in( $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnId, $inSet ) );
$itemWhere[] = array( Db::i()->in( $contentItemClass::$databasePrefix . $contentItemClass::$databaseColumnId, $inSet ) );
$insertFromSelect = \IPS\Db::i()->select( "'" . str_replace( '\\', '\\\\', $contentItemClass ) . "', `{$contentItemClass::$databasePrefix}{$contentItemClass::$databaseColumnId}`, `{$itemAuthorColumn}`, `{$itemDateColumn}`" , $contentItemClass::$databaseTable, $itemWhere );
\IPS\Db::i()->replace( 'core_item_member_map', $insertFromSelect, TRUE );
try
{
$seen = array();
/* We don't want a complex query on a large table (forums_posts) hitting a write server, so split this for forums to select (read server) and insert (write server) */
foreach( Db::i()->select( "`{$authorColumn}`, `{$commentItemField}`", $commentClass::$databaseTable, $where, NULL, 1 ) as $row )
{
$key = $row[ $authorColumn ] . '_' . $row[ $commentItemField ];
if ( isset( $seen[ $key ] ) or ! $row[ $authorColumn ] )
{
continue;
}
$seen[ $key ] = true;
Db::i()->insert( 'core_item_member_map', [
'map_member_id' => $row[ $authorColumn ],
'map_item_id' => $row[ $commentItemField ],
'map_latest_date' => time(),
'map_class' => $contentItemClass
] );
}
}
catch( Exception ) { }
}
/* If forums are being used for Pages comments, we don't want to look at those */
@@ -285,12 +318,12 @@ trait Statistics
if ( isset( $commentClass::$databaseColumnMap['approved'] ) )
{
$approvedColumn = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['approved'];
$where[] = array(\IPS\Db::i()->in( $approvedColumn, array(1, 2) )); # We want approved comments but also comments hidden because the item is hidden
$where[] = array(Db::i()->in( $approvedColumn, array(1, 2) )); # We want approved comments but also comments hidden because the item is hidden
}
if ( isset( $commentClass::$databaseColumnMap['hidden'] ) )
{
$hiddenColumn = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['hidden'];
$where[] = array(\IPS\Db::i()->in( $hiddenColumn, array(0, 2) )); # We want approved comments but also comments hidden because the item is hidden
$where[] = array(Db::i()->in( $hiddenColumn, array(0, 2) )); # We want approved comments but also comments hidden because the item is hidden
}
if ( $commentClass::commentWhere() !== NULL )
@@ -298,39 +331,10 @@ trait Statistics
$where[] = $commentClass::commentWhere();
}
$where[] = array(\IPS\Db::i()->in( $commentItemField, $inSet ));
$where[] = array(Db::i()->in( $commentItemField, $inSet ));
try
{
$seen = array();
/* If we don't have member(s) in the where clause, or we have a lot of member ids in the where clause, we can't do individual inserts as this could be 10,000+ inserts on very large topics */
if ( ! count( $memberIds ) or ( count( $memberIds ) > 100 ) )
{
$insertFromSelect = \IPS\Db::i()->select( "DISTINCT `{$authorColumn}`, `{$commentItemField}`, UNIX_TIMESTAMP(), '" . str_replace( '\\', '\\\\', $contentItemClass ) . "'", $commentClass::$databaseTable, $where );
\IPS\Db::i()->replace( 'core_item_member_map', [ 'map_member_id, map_item_id, map_latest_date, map_class', $insertFromSelect ], TRUE );
}
else
{
foreach( \IPS\Db::i()->select( "DISTINCT `{$authorColumn}` as {$authorColumn}, `{$commentItemField}`", $commentClass::$databaseTable, $where ) as $row )
{
$key = $row[ $authorColumn ] . '_' . $row[ $commentItemField ];
if ( isset( $seen[ $key ] ) or ! $row[ $authorColumn ] )
{
continue;
}
$seen[ $key ] = true;
\IPS\Db::i()->insert( 'core_item_member_map', [
'map_member_id' => $row[ $authorColumn ],
'map_item_id' => $row[ $commentItemField ],
'map_latest_date' => time(),
'map_class' => $contentItemClass
] );
}
}
}
catch( Exception ) { }
$insertFromSelect = Db::i()->select( "DISTINCT `{$authorColumn}`, `{$commentItemField}`, UNIX_TIMESTAMP(), '" . str_replace( '\\', '\\\\', $contentItemClass ) . "'", $commentClass::$databaseTable, $where );
Db::i()->replace( 'core_item_member_map', [ 'map_member_id, map_item_id, map_latest_date, map_class', $insertFromSelect ], TRUE );
}
}