Version 4.5.0

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

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
<?php
/**
* @brief GraphQL: API query controller
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 15 Oct 2019
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Core controller for GraphQL API
* @todo maybe this shouldn't be a class since it only has a static method?
*/
abstract class _AppQueries
{
/**
* Get the supported query types in this app
*
* @return array
*/
public static function queries($app)
{
return [
'moduleAccess' => new \IPS\Api\GraphQL\Queries\ModuleAccess($app)
];
}
}
@@ -0,0 +1,61 @@
<?php
/**
* @brief GraphQL: Coverphoto field defintiion
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 10 May 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Fields;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* ForumType for GraphQL API
*/
abstract class _CoverPhotoField
{
/**
* Get root type
*
* @return array
*/
public static function getDefinition($name)
{
return [
'type' => new ObjectType([
'name' => $name,
'description' => 'Returns a cover photo',
'fields' => [
'image' => TypeRegistry::string(),
'offset' => TypeRegistry::int()
],
'resolveField' => function( $value, $args, $context, $info ) {
switch ($info->fieldName)
{
case 'image':
return ( $value->file ) ? (string) $value->file->url : null;
break;
case 'offset':
return ( $value->file ) ? $value->offset : null;
break;
}
}
]),
'description' => 'Returns a cover photo',
'resolve' => function ($thing) {
return $thing->coverPhoto();
}
];
}
}
@@ -0,0 +1,68 @@
<?php
/**
* @brief GraphQL: TagsEdit field defintiion
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 10 May 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Fields;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* TagsEditField for GraphQL API
*/
abstract class _TagsEditField
{
/**
* Get root type
*
* @return array
*/
public static function getDefinition($name)
{
return [
'type' => new ObjectType([
'name' => $name,
'fields' => [
'enabled' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($container, $args, $context) {
$contentClass = $container::$contentItemClass;
if( \in_array( 'IPS\Content\Tags', class_implements( $contentClass ) ) ){
return true;
}
return false;
}
],
'definedTags' => [
'type' => TypeRegistry::listOf( TypeRegistry::string() ),
'resolve' => function ($container, $args, $context) {
$contentClass = $container::$contentItemClass;
$tags = array_unique( $contentClass::definedTags( $container ) );
return $tags;
}
]
]
]),
'description' => 'Returns tag editing capabilities',
'resolve' => function ($container) {
return $container;
}
];
}
}
@@ -0,0 +1,65 @@
<?php
/**
* @brief GraphQL: ModuleAccess query
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 10 May 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Queries;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* ModuleAccess query for GraphQL API
*/
class _ModuleAccess
{
public function __construct($app)
{
$this->app = $app;
}
/*
* @brief Query description
*/
public static $description = "Returns module view permissions";
/*
* Query arguments
*/
public function args()
{
}
/**
* Return the query return type
*/
public function type()
{
return TypeRegistry::moduleAccess();
}
/**
* Resolves this query
*
* @param mixed Value passed into this resolver
* @param array Arguments
* @param array Context values
* @return \IPS\core\Stream
*/
public function resolve($val, $args, $context)
{
return $this->app;
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
/**
* @brief Safe GraphQL Exception (i.e. client can be informed of the error message)
* @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
* @since 3 Dec 2015
*/
namespace IPS\Api\GraphQL;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
use GraphQL\Error\ClientAware;
/**
* API Exception
*/
class _SafeException extends \Exception implements ClientAware
{
/**
* @brief Exception code
*/
public $exceptionCode;
/**
* @brief OAUth Error
*/
public $oauthError;
/**
* Constructor
*
* @param string $message Error Message
* @param string $code Code
* @param int $httpCode HTTP Error code
* @param string $oauthError Error Message for OAuth
* @return void
*/
public function __construct( $message, $code, $httpCode )
{
$this->exceptionCode = $code;
return parent::__construct( $message, $httpCode );
}
public function getCategory()
{
return 'clienterror';
}
public function isClientSafe()
{
return true;
}
}
+190
View File
@@ -0,0 +1,190 @@
<?php
/**
* @brief Type registry
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 3 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Type registry
*/
class _TypeRegistry
{
protected static $query;
protected static $mutation;
protected static $itemState;
protected static $image;
protected static $reputation;
protected static $richText;
protected static $url;
protected static $follow;
protected static $moduleAccess;
/**
* Constructor
*/
public function __construct()
{
// Defined to suppress static warnings
}
/**
* @return QueryType
*/
public static function query()
{
return self::$query ?: (self::$query = new \IPS\Api\GraphQL\Types\QueryType());
}
/**
* @return MutationType
*/
public static function mutation()
{
return self::$mutation ?: (self::$mutation = new \IPS\Api\GraphQL\Types\MutationType());
}
/**
* @return ItemStateType
*/
public static function itemState()
{
return self::$itemState ?: (self::$itemState = new \IPS\Api\GraphQL\Types\ItemStateType());
}
/**
* @return ImageType
*/
public static function image()
{
return self::$image ?: (self::$image = new \IPS\Api\GraphQL\Types\ImageType());
}
/**
* @return ReputationType
*/
public static function reputation()
{
return self::$reputation ?: (self::$reputation = new \IPS\Api\GraphQL\Types\ReputationType());
}
/**
* @return RichTextType
*/
public static function richText()
{
return self::$richText ?: (self::$richText = new \IPS\Api\GraphQL\Types\RichTextType());
}
/**
* @return URLType
*/
public static function url()
{
return self::$url ?: (self::$url = new \IPS\Api\GraphQL\Types\UrlType());
}
/**
* @return FollowType
*/
public static function follow()
{
return self::$follow ?: (self::$follow = new \IPS\Api\GraphQL\Types\FollowType());
}
/**
* @return ModuleAccessType
*/
public static function moduleAccess()
{
return self::$moduleAccess ?: (self::$moduleAccess = new \IPS\Api\GraphQL\Types\ModuleAccessType());
}
/**
* @return \GraphQL\Type\Definition\IDType
*/
public static function id()
{
return Type::id();
}
/**
* @return \GraphQL\Type\Definition\StringType
*/
public static function string()
{
return Type::string();
}
/**
* @return \GraphQL\Type\Definition\IntType
*/
public static function int()
{
return Type::int();
}
/**
* @return \GraphQL\Type\Definition\FloatType
*/
public static function float()
{
return Type::float();
}
/**
* @return \GraphQL\Type\Definition\BooleanType
*/
public static function boolean()
{
return Type::boolean();
}
/**
* @return \GraphQL\Type\Definition\ListOfType
*/
public static function listOf($type)
{
return new ListOfType($type);
}
/**
* @return \GraphQL\Type\Definition\EnumType
*/
public static function eNum($config)
{
return new EnumType($config);
}
public static function inputObjectType($config)
{
return new InputObjectType($config);
}
/**
* @param Type $type
* @return NonNull
*/
public static function nonNull($type)
{
return new NonNull($type);
}
}
+233
View File
@@ -0,0 +1,233 @@
<?php
/**
* @brief GraphQL: Follow type defintiion
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 3 Sept 2018
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* FollowType for GraphQL API
*/
class _FollowType extends ObjectType
{
/**
* Get root type
*
* @return array
*/
public function __construct()
{
$config = [
'name' => 'Follow',
'description' => 'Returns the follow data for a node, item or member',
'fields' => [
'id' => [
'type' => TypeRegistry::id(),
'resolve' => function ($followData) {
// This field allows Apollo to more effectively cache results. We don't include
// the member ID in the MD5 here though because it will change when logging in.
return md5( $followData['app'] . ';' . $followData['area'] . ';' . $followData['id'] );
}
],
'followID' => [
'type' => TypeRegistry::id(),
'resolve' => function ($followData) {
return md5( $followData['app'] . ';' . $followData['area'] . ';' . $followData['id'] . ';' . \IPS\Member::loggedIn()->member_id );
}
],
'isFollowing' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($followData) {
return \IPS\Member::loggedIn()->following( $followData['app'], $followData['area'], $followData['id'] );
}
],
'followType' => [
'type' => TypeRegistry::eNum([
'name' => 'core_Follow_followType',
'values' => ['NOT_FOLLOWING', 'PUBLIC', 'ANONYMOUS']
]),
'resolve' => function ($followData) {
if( !\IPS\Member::loggedIn()->following( $followData['app'], $followData['area'], $followData['id'] ) )
{
return 'NOT_FOLLOWING';
}
try
{
$current = \IPS\Db::i()->select( 'follow_is_anon', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_rel_id=? AND follow_member_id=?', $followData['app'], $followData['area'], $followData['id'], \IPS\Member::loggedIn()->member_id ), NULL, array(), NULL, NULL )->first();
return $current['follow_is_anon'] ? 'ANONYMOUS' : 'PUBLIC';
}
catch ( \UnderflowException $e )
{
return 'NOT_FOLLOWING';
}
}
],
'followCount' => [
'type' => TypeRegistry::int(),
'resolve' => function ($followData) {
if( isset( $followData['node'] ) && isset( $followData['nodeClass'] ) )
{
$nodeClass = $followData['nodeClass'];
return $nodeClass::$contentItemClass::containerFollowerCount( $followData['node'] );
}
elseif( isset( $followData['member'] ) )
{
return $followData['member']->followersCount();
}
else
{
return $followData['item']->followersCount();
}
}
],
'anonFollowCount' => [
'type' => TypeRegistry::int(),
'resolve' => function ($followData) {
if( isset( $followData['node'] ) && isset( $followData['nodeClass'] ) )
{
$nodeClass = $followData['nodeClass'];
return $nodeClass::$contentItemClass::containerFollowerCount( $followData['node'], $nodeClass::$contentItemClass::FOLLOW_ANONYMOUS );
}
elseif( isset( $followData['member'] ) )
{
return $followData['member']->followersCount( \IPS\Member::FOLLOW_ANONYMOUS );
}
else
{
return $followData['item']->followersCount( $followData['itemClass']::FOLLOW_ANONYMOUS );
}
}
],
'followers' => [
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::member() ),
'args' => [
'offset' => [
'type' => TypeRegistry::int(),
'defaultValue' => 0
],
'limit' => [
'type' => TypeRegistry::int(),
'defaultValue' => 25
]
],
'resolve' => function ($followData, $args) {
$offset = max( $args['offset'], 0 );
$limit = min( $args['limit'], 50 );
if( isset( $followData['node'] ) && isset( $followData['nodeClass'] ) )
{
$nodeClass = $followData['nodeClass'];
$_followers = $nodeClass::$contentItemClass::containerFollowers( $followData['node'], $nodeClass::$contentItemClass::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( $offset, $limit ), 'name' );
}
elseif( isset( $followData['member'] ) )
{
$_followers = $followData['member']->followers( \IPS\Member::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( $offset, $limit ), 'name' );
}
else
{
$itemClass = $followData['itemClass'];
$_followers = $followData['item']->followers( $itemClass::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( $offset, $limit ), 'name' );
}
if( $_followers === NULL )
{
return NULL;
}
$followers = array();
foreach( $_followers as $followerRow )
{
$followers[] = \IPS\Member::load( $followerRow['follow_member_id'] );
}
return $followers;
}
],
// @todo this will need to be extensively modified when I add support for following clubs
'followOptions' => [
'type' => TypeRegistry::listOf( new ObjectType([
'name' => 'core_Follow_Options',
'fields' => [
'type' => TypeRegistry::string(),
'selected' => TypeRegistry::boolean(),
'disabled' => TypeRegistry::boolean()
],
'resolveField' => function ($followOption, $args, $context, $info) {
return $followOption[ $info->fieldName ];
}
]) ),
'resolve' => function ($followData, $args) {
// No options if we're a guest
if( !\IPS\Member::loggedIn()->member_id )
{
return array();
}
/* Do we follow it? */
try
{
$current = \IPS\Db::i()->select( '*', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_rel_id=? AND follow_member_id=?', $followData['app'], $followData['area'], $followData['id'], \IPS\Member::loggedIn()->member_id ), NULL, array(), NULL, NULL )->first();
}
catch ( \UnderflowException $e )
{
$current = FALSE;
}
// What kind of following are we doing?
if( isset( $followData['node'] ) )
{
$followType = 'new_content';
}
elseif( isset( $followData['member'] ) )
{
$followType = 'follower_content';
}
else
{
$followType = 'new_comment';
}
$notificationConfiguration = \IPS\Member::loggedIn()->notificationsConfiguration();
$notificationConfiguration = isset( $notificationConfiguration[ $followType ] ) ? $notificationConfiguration[ $followType ] : array();
// Now figure out which options we can have
$options = array( array(
'type' => 'immediate',
'disabled' => empty( $notificationConfiguration ), // If notification configuration is empty, show but disable 'immediate'
'selected' => ( $current && $current['follow_notify_freq'] === 'immediate' )
) );
if( !isset( $followData['member'] ) )
{
$options[] = array('type' => 'weekly', 'disabled' => false, 'selected' => ( $current && $current['follow_notify_freq'] === 'weekly' ) );
$options[] = array('type' => 'daily', 'disabled' => false, 'selected' => ( $current && $current['follow_notify_freq'] === 'daily' ) );
$options[] = array('type' => 'none', 'disabled' => false, 'selected' => ( $current && $current['follow_notify_freq'] === 'none' ) );
}
return $options;
}
]
]
];
parent::__construct( $config );
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
/**
* @brief GraphQL: Image Type
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 22 May 2019
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* ImageType for GraphQL API
*/
class _ImageType extends ObjectType
{
/**
* Get object type
*
* @return ObjectType
*/
public function __construct()
{
$config = [
'name' => 'Image',
'description' => 'Represents an image with width/height data',
'fields' => function () {
return [
'url' => [
'type' => TypeRegistry::string(),
'description' => "The URL to the image",
],
'width' => [
'type' => TypeRegistry::int(),
'description' => "The width in pixels",
],
'height' => [
'type' => TypeRegistry::int(),
'description' => "The height in pixels",
],
];
},
'resolveField' => function ($data, $args, $context, $info) {
return $data[ $info->fieldName ];
}
];
parent::__construct($config);
}
}
@@ -0,0 +1,54 @@
<?php
/**
* @brief GraphQL: Item State Type
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 21 Jun 2018
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\InputObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* ItemStateType for GraphQL API
*/
class _ItemStateType extends InputObjectType
{
/**
* Get object type
*
* @return ObjectType
*/
public function __construct()
{
$config = [
'name' => 'ItemState',
'fields' => [
'locked' => TypeRegistry::boolean(),
'featured' => TypeRegistry::boolean(),
'pinned' => TypeRegistry::boolean(),
'hidden' => TypeRegistry::eNum([
'name' => 'hiddenState',
'values' => [
'visible' => 0,
'unapproved' => -1,
'hidden' => 1
]
])
]
];
parent::__construct( $config );
}
}
@@ -0,0 +1,66 @@
<?php
/**
* @brief GraphQL: ModuleAccessType
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 15 Oct 2019
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* ModuleAccessType for GraphQL API
*/
class _ModuleAccessType extends ObjectType
{
/**
* Get object type
*
* @return ObjectType
*/
public function __construct()
{
$config = [
'name' => "ModuleAccess",
'description' => "Returns permissions for modules in the platform",
'fields' => function () {
return [
'view' => [
'type' => TypeRegistry::boolean(),
'args' => [
'module' => TypeRegistry::string()
],
'description' => "Returns the member's access to this module",
'resolve' => function( $app, $args, $context, $info ) {
$canAccess = FALSE;
try
{
$canAccess = \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( $app, $args['module'], 'front' ) );
}
catch (\Exception $e)
{
// Just fall through so we return false
}
return $canAccess;
}
]
];
}
];
parent::__construct($config);
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/**
* @brief API Exception
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 3 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* API Exception
*/
class _MutationType extends ObjectType
{
public function __construct()
{
$config = [
'name' => 'Mutation',
'fields' => function () {
$_fields = array();
foreach( \IPS\Application::enabledApplications() as $key => $app )
{
try
{
$appMutationClass = "\\IPS\\" . $app->directory . "\\api\\GraphQL\\Mutation";
if( !class_exists( $appMutationClass ) )
{
continue;
}
$mutationFields = array();
$mutationType = $appMutationClass::mutations();
foreach( $mutationType as $type => $mutationClass )
{
$mutationFields[ $type ] = array(
'type' => $mutationClass->type(),
'description' => $mutationClass::$description,
'args' => $mutationClass->args()
);
}
$_fields[ 'mutate' . ucfirst($app->directory) ] = new \GraphQL\Type\Definition\ObjectType( array(
'name' => 'mutate_' . ucfirst($app->directory),
'fields' => $mutationFields
) );
}
catch ( \Exception $err )
{ }
}
return $_fields;
},
'resolveField' => function ($val, $args, $context, $info) {
$name = str_replace( 'mutate', '', $info->fieldName );
try
{
$appMutationClass = '\\IPS\\' . $name . '\\api\\GraphQL\\Mutation';
$mutationResolvers = array();
$mutationType = $appMutationClass::mutations();
foreach( $mutationType as $type => $mutationClass )
{
$mutationResolvers[ $type ] = function($val, $args, $context, $info) use ($mutationClass)
{
return $mutationClass->resolve($val, $args, $context, $info);
};
}
return $mutationResolvers;
$mutationClass = new $className;
return $mutationClass->resolve($val, $args, $context, $info);
}
catch ( \Exception $e )
{
throw new \IPS\Api\Exception( 'NO_RESOLVE', '3S291/2', 405 );
}
}
];
parent::__construct( $config );
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
/**
* @brief API Exception
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 3 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* API Exception
*/
class _QueryType extends ObjectType
{
public function __construct()
{
$config = [
'name' => 'Query',
'fields' => function () {
$_fields = array();
foreach( \IPS\Application::enabledApplications() as $key => $app )
{
try
{
$globalAppQueries = \IPS\Api\GraphQL\AppQueries::queries($app->directory);
$appQueryClass = "\\IPS\\" . $app->directory . "\\api\\GraphQL\\Query";
if( !class_exists( $appQueryClass ) )
{
continue;
}
$queryFields = array();
$queryTypes = $appQueryClass::queries();
// Add the queries that *every* app receives
foreach( $globalAppQueries as $globalType => $globalQueryClass )
{
$queryFields[ $globalType ] = array(
'type' => $globalQueryClass->type(),
'description' => $globalQueryClass::$description,
'args' => $globalQueryClass->args()
);
}
// Add each app's individual queries
foreach( $queryTypes as $type => $queryClass )
{
$queryFields[ $type ] = array(
'type' => $queryClass->type(),
'description' => $queryClass::$description,
'args' => $queryClass->args()
);
}
$_fields[ $app->directory ] = new \GraphQL\Type\Definition\ObjectType( array(
'name' => $app->directory,
'fields' => $queryFields
) );
}
catch ( \Exception $e )
{
print_r( $e );
exit;
}
}
return $_fields;
},
'resolveField' => function ($val, $args, $context, $info) {
try
{
$globalAppQueries = \IPS\Api\GraphQL\AppQueries::queries($info->fieldName);
$appQueryClass = '\\IPS\\' . $info->fieldName . '\\api\\GraphQL\\Query';
$queryResolvers = array();
$queryTypes = $appQueryClass::queries();
// Resolve the global app queries that every app receives
foreach( $globalAppQueries as $globalType => $globalQueryClass )
{
$queryResolvers[ $globalType ] = function ($val, $args, $context, $info) use ($globalQueryClass)
{
return $globalQueryClass->resolve( $val, $args, $context, $info);
};
}
// Resolve the individual per-app queries
foreach( $queryTypes as $type => $queryClass )
{
$queryResolvers[ $type ] = function($val, $args, $context, $info) use ($queryClass)
{
return $queryClass->resolve($val, $args, $context, $info);
};
}
return $queryResolvers;
}
catch ( \Exception $e )
{
throw new \IPS\Api\Exception( 'NO_RESOLVE', '3S291/2', 405 );
}
}
];
parent::__construct( $config );
}
}
+189
View File
@@ -0,0 +1,189 @@
<?php
/**
* @brief GraphQL: Reputation type defintiion
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 10 May 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* ReputationType for GraphQL API
*/
class _ReputationType extends ObjectType
{
/**
* Get root type
*
* @return array
*/
public function __construct()
{
$config = [
'name' => 'Reputation',
'description' => 'Returns the reputation',
'fields' => [
'reactionCount' => [
'type' => TypeRegistry::int(),
'resolve' => function ($content, $args) {
if( !\IPS\Settings::i()->reputation_enabled )
{
return 0;
}
return $content->reactionCount();
}
],
'canViewReps' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($content, $args) {
return \IPS\Settings::i()->reputation_enabled && \IPS\Member::loggedIn()->group['gbw_view_reps'];
}
],
'canReact' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($content, $args) {
return \IPS\Settings::i()->reputation_enabled && $content->canReact();
}
],
'hasReacted' => [
'type' => TypeRegistry::boolean()
],
'isLikeMode' => [
'type' => TypeRegistry::boolean(),
'resolve' => function ($content, $args) {
return \IPS\Settings::i()->reputation_enabled && \IPS\Content\Reaction::isLikeMode();
}
],
'givenReaction' => [
'type' => \IPS\core\api\GraphQL\TypeRegistry::contentReaction()
],
'defaultReaction' => [
'type' => \IPS\core\api\GraphQL\TypeRegistry::contentReaction(),
],
'availableReactions' => [
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::contentReaction() )
],
'reactions' => [
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::contentReaction() ),
'resolve' => function ($content) {
if( !\IPS\Settings::i()->reputation_enabled )
{
return NULL;
}
$return = array();
$idColumn = $content::$databaseColumnId;
foreach( $content->reactBlurb() as $key => $count )
{
$return[] = array( 'id' => md5( \get_class( $content ) . '-' . $content->$idColumn ) . '-' . $key, 'reactionId' => $key, 'count' => $count );
}
return $return;
}
],
'whoReacted' => [
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::member() ),
'args' => [
'id' => TypeRegistry::int(),
'offset' => [
'type' => TypeRegistry::int(),
'defaultValue' => 0
],
'limit' => [
'type' => TypeRegistry::int(),
'defaultValue' => 25
]
],
'resolve' => function ($content, $args) {
if( !\IPS\Settings::i()->reputation_enabled || !\IPS\Member::loggedIn()->group['gbw_view_reps'] )
{
return NULL;
}
$reaction = NULL;
$members = array();
$offset = max( $args['offset'], 0 );
$limit = min( $args['limit'], 50 );
if( isset( $args['id'] ) )
{
$reaction = \intval( $args['id']);
}
foreach( \IPS\Db::i()->select( '*', 'core_reputation_index', $content->getReactionWhereClause( $reaction ), 'rep_date desc', array( $offset, $limit ) )->join( 'core_reactions', 'reaction=reaction_id' ) AS $reaction )
{
$members[] = \IPS\Member::load( $reaction['member_id'] );
}
return $members;
}
]
],
'resolveField' => function ($content, $args, $context, $info) {
if( !\IPS\Settings::i()->reputation_enabled )
{
return NULL;
}
$enabledReactions = \IPS\Content\Reaction::enabledReactions();
$defaultReaction = reset( $enabledReactions );
$reacted = $content->reacted();
switch( $info->fieldName )
{
case 'hasReacted':
return ( $reacted and isset( $enabledReactions[ $reacted->id ] ) );
break;
case 'givenReaction':
if( !$reacted )
{
return NULL;
}
$reaction = isset( $enabledReactions[ $reacted->id ] ) ? $enabledReactions[ $reacted->id ] : $defaultReaction;
return array(
'id' => $reaction->id,
'reaction' => $reaction
);
break;
case 'defaultReaction':
return array(
'id' => $defaultReaction->id,
'reaction' => $defaultReaction
);
break;
case 'availableReactions':
$reactions = array();
foreach( $enabledReactions as $reaction )
{
$reactions[] = array(
'id' => $reaction->id,
'reaction' => $reaction
);
}
return $reactions;
break;
}
}
];
parent::__construct( $config );
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
/**
* @brief GraphQL: RichText Type
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 7 May 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* RichTextType for GraphQL API
*/
class _RichTextType extends ObjectType
{
/**
* Get object type
*
* @return ObjectType
*/
public function __construct()
{
$config = [
'name' => 'core_RichText',
'description' => 'Represents an internal URL',
'fields' => [
'plain' => [
'type' => TypeRegistry::string(),
'description' => "Returns plain-text version of the text",
'args' => [
'singleLine' => [
'type' => TypeRegistry::boolean(),
'description' => "Should linebreaks be removed?",
'defaultValue' => TRUE
],
'truncateLength' => [
'type' => TypeRegistry::int(),
'description' => 'Characters to truncate on. Only available if stripped option is true.',
'defaultValue' => 0
]
],
'resolve' => function ($string, $args) {
/* Remove stuff we don't want to include (quotes, spoilers, scripts) */
$string = \IPS\Text\Parser::removeElements( $string, array( 'blockquote', 'script', 'div[class=ipsSpoiler]' ) );
/* Put a break in places where we actually want one */
$string = str_replace( array( '</p>', '</h1>', '</h2>', '</h3>', '</h4>', '</h5>', '</h6>', '</li>' ), '<br>', $string );
/* Strip all other tags */
$string = strip_tags( $string, '<br>' );
$string = html_entity_decode( $string );
/* Fix linebreaks */
$string = preg_replace( "/\r|\n|\t/", '', $string );
$string = str_replace( '<br>', $args['singleLine'] ? ' ' : "\n", $string );
/* Trim whitespace */
$string = trim( $string );
/* Truncate */
if( $args['truncateLength'] )
{
$string = mb_substr( $string, 0, $args['truncateLength'] );
}
/* Return */
return $string;
}
],
'original' => [
'type' => TypeRegistry::string(),
'description' => "Returns original rich text (i.e. containing full rich markup)",
'args' => [
'removeLazyLoad' => [
'type' => TypeRegistry::boolean(),
'description' => "Remove lazy-load placeholders?",
'defaultValue' => TRUE
]
],
'resolve' => function ($string, $args) {
if( $args['removeLazyLoad'] )
{
$string = \IPS\Text\Parser::removeLazyLoad( $string );
}
\IPS\Output::i()->parseFileObjectUrls( $string );
return $string;
}
]
]
];
parent::__construct($config);
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
/**
* @brief GraphQL: URL Type
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 7 May 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Api\GraphQL\Types;
use GraphQL\Type\Definition\ObjectType;
use IPS\Api\GraphQL\TypeRegistry;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* URLType for GraphQL API
*/
class _UrlType extends ObjectType
{
/**
* Get object type
*
* @return ObjectType
*/
public function __construct()
{
$config = [
'name' => 'URL',
'description' => 'Represents an internal URL',
'fields' => function () {
return [
'full' => [
'type' => TypeRegistry::string(),
'description' => "The full URL",
'resolve' => function ($url, $args, $context) {
return (string) $url;
}
],
'app' => [
'type' => TypeRegistry::string(),
'description' => "Application",
],
'module' => [
'type' => TypeRegistry::string(),
'description' => "Module",
],
'controller' => [
'type' => TypeRegistry::string(),
'description' => "Controller",
],
'query' => [
'type' => TypeRegistry::listOf( new ObjectType([
'name' => 'URL_query',
'fields' => [
'key' => TypeRegistry::string(),
'value' => TypeRegistry::string()
],
'resolveField' => function ($query, $args, $context, $info) {
return $query[ $info->fieldName ];
}
])),
'resolve' => function ($url) {
if( !( $url instanceof \IPS\Http\Url ) )
{
$url = \IPS\Http\Url::createFromString( $url );
}
$pieces = array();
foreach( $url->queryString as $key => $value )
{
$pieces[] = array(
'key' => $key,
'value' => $value
);
}
return $pieces;
}
]
];
},
'resolveField' => function ($url, $args, $context, $info) {
if( !( $url instanceof \IPS\Http\Url ) )
{
$url = \IPS\Http\Url::createFromString( $url );
}
$urlPieces = $url->hiddenQueryString;
if( \in_array( $info->fieldName, array('app', 'module', 'controller') ) )
{
return $urlPieces[ $info->fieldName ];
}
return (string) $url;
}
];
parent::__construct($config);
}
}