Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
+69
-53
@@ -11,70 +11,86 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DirectoryIterator;
|
||||
use IPS\Application;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Settings;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use RuntimeException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function get_called_class;
|
||||
use function in_array;
|
||||
use function method_exists;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base API Controller
|
||||
*/
|
||||
abstract class _Controller
|
||||
abstract class Controller
|
||||
{
|
||||
/**
|
||||
* @brief API Key
|
||||
*/
|
||||
protected $apiKey;
|
||||
protected ?Key $apiKey = null;
|
||||
|
||||
/**
|
||||
* @brief OAuth Client
|
||||
*/
|
||||
protected $client;
|
||||
protected ?OAuthClient $client = null;
|
||||
|
||||
/**
|
||||
* @brief OAuth Authenticated Member
|
||||
*/
|
||||
protected $member;
|
||||
protected ?Member $member = null;
|
||||
|
||||
/**
|
||||
* @brief OAuth Scopes
|
||||
*/
|
||||
protected $scopes;
|
||||
protected ?array $scopes = null;
|
||||
|
||||
/**
|
||||
* @brief Used OAuth Scope
|
||||
*/
|
||||
protected $usedScope;
|
||||
protected ?string $usedScope = null;
|
||||
|
||||
/**
|
||||
* @brief Parameters to mask in logs. Keys are the method names and values an array of field or request keys.
|
||||
*/
|
||||
public $parametersToMask = array();
|
||||
public array $parametersToMask = array();
|
||||
|
||||
/**
|
||||
* @brief Name of the method we called
|
||||
*/
|
||||
public $methodCalled = NULL;
|
||||
public ?string $methodCalled = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Api\Key $apiKey The API key being used to access, if applicable
|
||||
* @param Key|null $apiKey The API key being used to access, if applicable
|
||||
* @param array|null $accessToken Access token for OAuth-authenticated requests
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Api\Key $apiKey = NULL, $accessToken = NULL )
|
||||
public function __construct( ?Key $apiKey = NULL, ?array $accessToken = NULL )
|
||||
{
|
||||
$this->apiKey = $apiKey;
|
||||
|
||||
if ( $accessToken )
|
||||
{
|
||||
$this->client = \IPS\Api\OAuthClient::load( $accessToken['client_id'] );
|
||||
$this->client = OAuthClient::load( $accessToken['client_id'] );
|
||||
$this->scopes = $accessToken['scope'] ? json_decode( $accessToken['scope'] ) : NULL;
|
||||
if ( $accessToken['member_id'] )
|
||||
{
|
||||
$this->member = \IPS\Member::load( $accessToken['member_id'] );
|
||||
$this->member = Member::load( $accessToken['member_id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +103,7 @@ abstract class _Controller
|
||||
* @param string $method Method
|
||||
* @return bool
|
||||
*/
|
||||
protected function canAccess( $app, $controller, $method )
|
||||
protected function canAccess( string $app, string $controller, string $method ) : bool
|
||||
{
|
||||
if ( $this->apiKey )
|
||||
{
|
||||
@@ -111,7 +127,7 @@ abstract class _Controller
|
||||
* @param string $method Method
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldLog( $app, $controller, $method )
|
||||
protected function shouldLog( string $app, string $controller, string $method ) : bool
|
||||
{
|
||||
if ( $this->apiKey )
|
||||
{
|
||||
@@ -128,47 +144,47 @@ abstract class _Controller
|
||||
*
|
||||
* @param array $pathBits The parts to the path called
|
||||
* @param bool $shouldLog Gets set to TRUE if this call should log
|
||||
* @return \IPS\Api\Response
|
||||
* @throws \IPS\Api\Exception
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
*/
|
||||
public function execute( $pathBits, &$shouldLog )
|
||||
public function execute( array $pathBits, bool &$shouldLog ) : Response
|
||||
{
|
||||
$method = ( isset( $_SERVER['REQUEST_METHOD'] ) and \in_array( mb_strtoupper( $_SERVER['REQUEST_METHOD'] ), array( 'GET', 'POST', 'PUT', 'DELETE' ) ) ) ? mb_strtoupper( $_SERVER['REQUEST_METHOD'] ) : 'GET';
|
||||
$method = ( isset( $_SERVER['REQUEST_METHOD'] ) and in_array( mb_strtoupper( $_SERVER['REQUEST_METHOD'] ), array( 'GET', 'POST', 'PUT', 'DELETE' ) ) ) ? mb_strtoupper( $_SERVER['REQUEST_METHOD'] ) : 'GET';
|
||||
$params = array();
|
||||
|
||||
try
|
||||
{
|
||||
$endpointData = $this->_getEndpoint( $pathBits, $method );
|
||||
}
|
||||
catch ( \RuntimeException $e )
|
||||
catch ( RuntimeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_ENDPOINT', '2S291/1', 404 );
|
||||
throw new Exception( 'NO_ENDPOINT', '2S291/1', 404 );
|
||||
}
|
||||
|
||||
if ( method_exists( $this, "{$method}{$endpointData['endpoint']}" ) )
|
||||
{
|
||||
preg_match( '/^IPS\\\(.+?)\\\api\\\(.+?)$/', \get_called_class(), $matches );
|
||||
preg_match( '/^IPS\\\(.+?)\\\api\\\(.+?)$/', get_called_class(), $matches );
|
||||
|
||||
$shouldLog = $this->shouldLog( $matches[1], $matches[2], "{$method}{$endpointData['endpoint']}" );
|
||||
if ( !$this->canAccess( $matches[1], $matches[2], "{$method}{$endpointData['endpoint']}" ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2S291/7', 403, 'insufficient_scope' );
|
||||
throw new Exception( 'NO_PERMISSION', '2S291/7', 403, 'insufficient_scope' );
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionMethod( $this, "{$method}{$endpointData['endpoint']}" );
|
||||
$reflection = new ReflectionMethod( $this, "{$method}{$endpointData['endpoint']}" );
|
||||
$docBlock = static::decodeDocblock( $reflection->getDocComment() );
|
||||
if ( !$this->member )
|
||||
{
|
||||
if ( isset( $docBlock['details']['apimemberonly'] ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2S291/5', 403, 'insufficient_scope' );
|
||||
throw new Exception( 'NO_PERMISSION', '2S291/5', 403, 'insufficient_scope' );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isset( $docBlock['details']['apiclientonly'] ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2S291/6', 403, 'insufficient_scope' );
|
||||
throw new Exception( 'NO_PERMISSION', '2S291/6', 403, 'insufficient_scope' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +197,7 @@ abstract class _Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'BAD_METHOD', '3S291/2', 405 );
|
||||
throw new Exception( 'BAD_METHOD', '3S291/2', 405 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,21 +207,21 @@ abstract class _Controller
|
||||
* @param array $pathBits The parts to the path called
|
||||
* @param string $method HTTP method verb
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function _getEndpoint( $pathBits, $method = 'GET' )
|
||||
protected function _getEndpoint( array $pathBits, string $method = 'GET' ) : array
|
||||
{
|
||||
$endpoint = NULL;
|
||||
$params = array();
|
||||
|
||||
if ( \count( $pathBits ) === 0 )
|
||||
if ( count( $pathBits ) === 0 )
|
||||
{
|
||||
$endpoint = 'index';
|
||||
}
|
||||
elseif ( \count( $pathBits ) === 1 )
|
||||
elseif ( count( $pathBits ) === 1 )
|
||||
{
|
||||
/* Sometimes we want to do something like /api/core/clubs/contenttypes but this is treated as an item */
|
||||
if( \method_exists( $this, $method . $pathBits[0] ) )
|
||||
if( method_exists( $this, $method . $pathBits[0] ) )
|
||||
{
|
||||
$endpoint = array_shift( $pathBits );
|
||||
}
|
||||
@@ -215,18 +231,18 @@ abstract class _Controller
|
||||
$endpoint = 'item';
|
||||
}
|
||||
}
|
||||
elseif ( \count( $pathBits ) === 2 )
|
||||
elseif ( count( $pathBits ) === 2 )
|
||||
{
|
||||
$params[] = array_shift( $pathBits );
|
||||
$endpoint = 'item_' . array_shift( $pathBits );
|
||||
}
|
||||
elseif ( \count( $pathBits ) === 3 )
|
||||
elseif ( count( $pathBits ) === 3 )
|
||||
{
|
||||
$params[] = array_shift( $pathBits );
|
||||
$endpoint = 'item_' . array_shift( $pathBits );
|
||||
$params[] = array_shift( $pathBits );
|
||||
}
|
||||
elseif ( \count( $pathBits ) === 4 )
|
||||
elseif ( count( $pathBits ) === 4 )
|
||||
{
|
||||
$params[] = array_shift( $pathBits );
|
||||
$endpoint = 'item_' . array_shift( $pathBits );
|
||||
@@ -235,7 +251,7 @@ abstract class _Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \RuntimeException;
|
||||
throw new RuntimeException;
|
||||
}
|
||||
|
||||
return array( 'endpoint' => $endpoint, 'params' => $params );
|
||||
@@ -248,17 +264,17 @@ abstract class _Controller
|
||||
* @param bool $includeAll Should also disabled applications be returned
|
||||
* @return array
|
||||
*/
|
||||
public static function getAllEndpoints( $type = NULL, $includeAll = FALSE )
|
||||
public static function getAllEndpoints( ?string $type = NULL, bool $includeAll = FALSE ) : array
|
||||
{
|
||||
$return = array();
|
||||
foreach ( \IPS\Application::applications() as $app )
|
||||
foreach ( Application::applications() as $app )
|
||||
{
|
||||
if ( $app->enabled OR $includeAll )
|
||||
{
|
||||
$apiDir = $app->getApplicationPath() . '/api';
|
||||
if ( file_exists( $apiDir ) )
|
||||
{
|
||||
$directory = new \DirectoryIterator( $apiDir );
|
||||
$directory = new DirectoryIterator( $apiDir );
|
||||
foreach ( $directory as $file )
|
||||
{
|
||||
if ( !$file->isDot() and mb_substr( $file, 0, 1 ) != '.' )
|
||||
@@ -267,7 +283,7 @@ abstract class _Controller
|
||||
$class = 'IPS\\' . $app->directory . '\\api\\' . $controllerName;
|
||||
if( class_exists( $class ) )
|
||||
{
|
||||
$reflection = new \ReflectionClass( $class );
|
||||
$reflection = new ReflectionClass( $class );
|
||||
foreach ( $reflection->getMethods() as $method )
|
||||
{
|
||||
if ( $method->getName() != 'execute' and !$method->isStatic() and $method->isPublic() and mb_substr( $method->getName(), 0, 1 ) != '_' )
|
||||
@@ -295,7 +311,7 @@ abstract class _Controller
|
||||
* @param string $comment The docblock comment
|
||||
* @return array
|
||||
*/
|
||||
public static function decodeDocblock( $comment )
|
||||
public static function decodeDocblock( string $comment ) : array
|
||||
{
|
||||
$comment = explode( "\n", $comment );
|
||||
array_shift( $comment );
|
||||
@@ -350,31 +366,31 @@ abstract class _Controller
|
||||
* @param bool $includeBaseUrl Whether or not to include the URL base in the endpoint URL
|
||||
* @return string
|
||||
*/
|
||||
public static function parseEndpointForDisplay( $endpoint, $size='small', $includeBaseUrl=FALSE )
|
||||
public static function parseEndpointForDisplay( string $endpoint, string $size='small', bool $includeBaseUrl=FALSE ) : string
|
||||
{
|
||||
$badgeStyles = array(
|
||||
'GET' => 'ipsBadge_positive',
|
||||
'POST' => 'ipsBadge_style2',
|
||||
'DELETE' => 'ipsBadge_negative',
|
||||
'PUT' => 'ipsBadge_intermediary'
|
||||
'GET' => 'ipsBadge--positive',
|
||||
'POST' => 'ipsBadge--style2',
|
||||
'DELETE' => 'ipsBadge--negative',
|
||||
'PUT' => 'ipsBadge--intermediary'
|
||||
);
|
||||
|
||||
$pieces = explode( ' ', $endpoint );
|
||||
if ( !\in_array( $pieces[0], array_keys( $badgeStyles ) ) )
|
||||
if ( !in_array( $pieces[0], array_keys( $badgeStyles ) ) )
|
||||
{
|
||||
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( 'api_endpoint_phpdoc_error', FALSE, array( "sprintf" => array( $endpoint ) ) ), '3S291/4', 500 );
|
||||
Output::i()->error( Member::loggedIn()->language()->addToStack( 'api_endpoint_phpdoc_error', FALSE, array( "sprintf" => array( $endpoint ) ) ), '3S291/4' );
|
||||
}
|
||||
$pieces[0] = "<span class='ipsBadge ipsBadge_{$size} " . $badgeStyles[ $pieces[0] ] . "'>" . $pieces[0] . "</span>";
|
||||
$pieces[0] = "<span class='ipsBadge ipsBadge--{$size} " . $badgeStyles[ $pieces[0] ] . "'>" . $pieces[0] . "</span>";
|
||||
|
||||
if ( $includeBaseUrl )
|
||||
{
|
||||
if ( \IPS\Settings::i()->use_friendly_urls and \IPS\Settings::i()->htaccess_mod_rewrite )
|
||||
if ( Settings::i()->use_friendly_urls and Settings::i()->htaccess_mod_rewrite )
|
||||
{
|
||||
$url = \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->base_url, '/' ) . '/api' );
|
||||
$url = Url::external( rtrim( Settings::i()->base_url, '/' ) . '/api' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->base_url, '/' ) . '/api/index.php?' );
|
||||
$url = Url::external( rtrim( Settings::i()->base_url, '/' ) . '/api/index.php?' );
|
||||
}
|
||||
$pieces[1] = $url . $pieces[1];
|
||||
}
|
||||
|
||||
@@ -11,28 +11,39 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use ReflectionMethod;
|
||||
use function defined;
|
||||
use function in_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation Helper
|
||||
*/
|
||||
abstract class _DocumentationHelper
|
||||
abstract class DocumentationHelper
|
||||
{
|
||||
|
||||
public static function getDescriptionForClass( $class )
|
||||
/**
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
public static function getDescriptionForClass( string $class ) : string
|
||||
{
|
||||
if ( method_exists( $class, 'apiOutput') )
|
||||
{
|
||||
$reflection = new \ReflectionMethod( $class, 'apiOutput' );
|
||||
$decoded = \IPS\Api\Controller::decodeDocblock( $reflection->getDocComment() );
|
||||
return \IPS\Theme::i()->getTemplate('api','core')->referenceTable( $decoded['details']['apiresponse'] );
|
||||
$reflection = new ReflectionMethod( $class, 'apiOutput' );
|
||||
$decoded = Controller::decodeDocblock( $reflection->getDocComment() );
|
||||
return Theme::i()->getTemplate('api','core')->referenceTable( $decoded['details']['apiresponse'] );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'global' )->block( '', \IPS\Member::loggedIn()->language()->addToStack( 'class_no_apioutput_method' ) );
|
||||
return Theme::i()->getTemplate( 'global' )->block( '', Member::loggedIn()->language()->addToStack( 'class_no_apioutput_method' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +53,7 @@ abstract class _DocumentationHelper
|
||||
* @param bool $exclude If FALSE, will include this class itself in the return array
|
||||
* @return array
|
||||
*/
|
||||
public static function getAdditionalClasses( $class, $exclude=FALSE )
|
||||
public static function getAdditionalClasses( string $class, bool $exclude=FALSE ) : array
|
||||
{
|
||||
if( !class_exists( $class ) or !method_exists( $class, 'apiOutput'))
|
||||
{
|
||||
@@ -50,15 +61,15 @@ abstract class _DocumentationHelper
|
||||
}
|
||||
|
||||
$return = $exclude ? array() : array( $class => $class );
|
||||
$reflection = new \ReflectionMethod( $class, 'apiOutput' );
|
||||
$decoded = \IPS\Api\Controller::decodeDocblock( $reflection->getDocComment() );
|
||||
$reflection = new ReflectionMethod( $class, 'apiOutput' );
|
||||
$decoded = Controller::decodeDocblock( $reflection->getDocComment() );
|
||||
foreach ( $decoded['details']['apiresponse'] as $response )
|
||||
{
|
||||
if ( mb_strpos( $response[0], '|' ) === FALSE AND !\in_array( $response[0], array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
|
||||
if ( mb_strpos( $response[0], '|' ) === FALSE AND !in_array( $response[0], array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
|
||||
{
|
||||
if ( mb_substr( $response[0], 0, 1 ) == '[' )
|
||||
{
|
||||
if ( !\in_array( mb_substr( $response[0], 1, -1 ), $return ) and !\in_array( mb_substr( $response[0], 1, -1 ), array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
|
||||
if ( !in_array( mb_substr( $response[0], 1, -1 ), $return ) and !in_array( mb_substr( $response[0], 1, -1 ), array( 'int', 'string', 'float', 'datetime', 'bool', 'object', 'array' ) ) )
|
||||
{
|
||||
if( $returned = static::getAdditionalClasses( mb_substr( $response[0], 1, -1 ) ) )
|
||||
{
|
||||
@@ -66,7 +77,7 @@ abstract class _DocumentationHelper
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ( !\in_array( $response[0], $return ) )
|
||||
elseif ( !in_array( $response[0], $return ) )
|
||||
{
|
||||
if( $returned = static::getAdditionalClasses( $response[0] ) )
|
||||
{
|
||||
|
||||
@@ -11,26 +11,30 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use Exception as PHPException;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Exception
|
||||
*/
|
||||
class _Exception extends \Exception
|
||||
class Exception extends PHPException
|
||||
{
|
||||
/**
|
||||
* @brief Exception code
|
||||
*/
|
||||
public $exceptionCode;
|
||||
public string $exceptionCode = '';
|
||||
|
||||
/**
|
||||
* @brief OAUth Error
|
||||
*/
|
||||
public $oauthError;
|
||||
public string $oauthError = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -41,10 +45,10 @@ class _Exception extends \Exception
|
||||
* @param string $oauthError Error Message for OAuth
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $message, $code, $httpCode, $oauthError = 'invalid_request' )
|
||||
public function __construct( string $message, string $code, int $httpCode, string $oauthError = 'invalid_request' )
|
||||
{
|
||||
$this->exceptionCode = $code;
|
||||
$this->oauthError = $oauthError;
|
||||
return parent::__construct( $message, $httpCode );
|
||||
parent::__construct( $message, $httpCode );
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@
|
||||
*/
|
||||
|
||||
namespace IPS\Api\GraphQL;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Api\GraphQL\Queries\ModuleAccess;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -24,18 +24,19 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
* Core controller for GraphQL API
|
||||
* @todo maybe this shouldn't be a class since it only has a static method?
|
||||
*/
|
||||
abstract class _AppQueries
|
||||
abstract class AppQueries
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the supported query types in this app
|
||||
*
|
||||
* @return array
|
||||
* @param string $app
|
||||
* @return array
|
||||
*/
|
||||
public static function queries($app): array
|
||||
public static function queries( string $app ): array
|
||||
{
|
||||
return [
|
||||
'moduleAccess' => new \IPS\Api\GraphQL\Queries\ModuleAccess($app)
|
||||
'moduleAccess' => new ModuleAccess($app)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -12,25 +12,27 @@
|
||||
namespace IPS\Api\GraphQL\Fields;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ForumType for GraphQL API
|
||||
*/
|
||||
abstract class _CoverPhotoField
|
||||
abstract class CoverPhotoField
|
||||
{
|
||||
/**
|
||||
* Get root type
|
||||
*
|
||||
* @return array
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public static function getDefinition($name): array
|
||||
public static function getDefinition( string $name ): array
|
||||
{
|
||||
return [
|
||||
'type' => new ObjectType([
|
||||
@@ -45,11 +47,10 @@ abstract class _CoverPhotoField
|
||||
{
|
||||
case 'image':
|
||||
return ( $value->file ) ? (string) $value->file->url : null;
|
||||
break;
|
||||
case 'offset':
|
||||
return ( $value->file ) ? $value->offset : null;
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
]),
|
||||
'description' => 'Returns a cover photo',
|
||||
|
||||
@@ -12,25 +12,28 @@
|
||||
namespace IPS\Api\GraphQL\Fields;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\IPS;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* TagsEditField for GraphQL API
|
||||
*/
|
||||
abstract class _TagsEditField
|
||||
abstract class TagsEditField
|
||||
{
|
||||
/**
|
||||
* Get root type
|
||||
*
|
||||
* @return array
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public static function getDefinition($name): array
|
||||
public static function getDefinition( string $name ): array
|
||||
{
|
||||
return [
|
||||
'type' => new ObjectType([
|
||||
@@ -41,7 +44,8 @@ abstract class _TagsEditField
|
||||
'resolve' => function ($container, $args, $context) {
|
||||
$contentClass = $container::$contentItemClass;
|
||||
|
||||
if( \in_array( 'IPS\Content\Tags', class_implements( $contentClass ) ) ){
|
||||
if( IPS::classUsesTrait( $contentClass, 'IPS\Content\Taggable' ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -52,8 +56,7 @@ abstract class _TagsEditField
|
||||
'type' => TypeRegistry::listOf( TypeRegistry::string() ),
|
||||
'resolve' => function ($container, $args, $context) {
|
||||
$contentClass = $container::$contentItemClass;
|
||||
$tags = array_unique( $contentClass::definedTags( $container ) );
|
||||
|
||||
$tags = array_unique( $contentClass::definedTags() );
|
||||
return $tags;
|
||||
}
|
||||
]
|
||||
|
||||
@@ -11,16 +11,26 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use GraphQL\Error\DebugFlag;
|
||||
use GraphQL\GraphQL as _GraphQL;
|
||||
use GraphQL\Type\Schema;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\IPS;
|
||||
use IPS\Member;
|
||||
use function defined;
|
||||
use const IPS\DEBUG_GRAPHQL;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base API Controller
|
||||
*/
|
||||
class _GraphQL
|
||||
class GraphQL
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
@@ -33,20 +43,20 @@ class _GraphQL
|
||||
*
|
||||
* @param string $query The query to execute
|
||||
* @param array $variables Variables to include in query
|
||||
* @param \IPS\Member|NULL $member Member to check or NULL for currently logged in member.
|
||||
* @param Member|null $member Member to check or NULL for currently logged in member.
|
||||
* @return array GraphQL response
|
||||
*/
|
||||
public static function execute( string $query, array $variables = [], ?\IPS\Member $member = NULL )
|
||||
public static function execute( string $query, array $variables = [], ?Member $member = NULL ) : array
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
$member = $member ?: Member::loggedIn();
|
||||
/* Register our GraphQL library */
|
||||
\IPS\IPS::$PSR0Namespaces['GraphQL'] = \IPS\ROOT_PATH . "/system/3rd_party/graphql-php";
|
||||
IPS::$PSR0Namespaces['GraphQL'] = \IPS\ROOT_PATH . "/system/3rd_party/graphql-php";
|
||||
|
||||
/* Execute! */
|
||||
$result = \GraphQL\GraphQL::executeQuery(
|
||||
new \GraphQL\Type\Schema([
|
||||
'query' => \IPS\Api\GraphQL\TypeRegistry::query(),
|
||||
'mutation' => \IPS\Api\GraphQL\TypeRegistry::mutation()
|
||||
$result = _GraphQL::executeQuery(
|
||||
new Schema([
|
||||
'query' => TypeRegistry::query(),
|
||||
'mutation' => TypeRegistry::mutation()
|
||||
]),
|
||||
$query,
|
||||
NULL, // $rootValue
|
||||
@@ -57,7 +67,6 @@ class _GraphQL
|
||||
);
|
||||
|
||||
/* Convert result into JSON and send */
|
||||
$output = $result->toArray( ( \IPS\IN_DEV OR \IPS\DEBUG_GRAPHQL ) ? \GraphQL\Error\DebugFlag::INCLUDE_DEBUG_MESSAGE | \GraphQL\Error\DebugFlag::INCLUDE_TRACE : false );
|
||||
return $output;
|
||||
return $result->toArray( ( \IPS\IN_DEV OR DEBUG_GRAPHQL ) ? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE : false );
|
||||
}
|
||||
}
|
||||
@@ -10,22 +10,26 @@
|
||||
*/
|
||||
|
||||
namespace IPS\Api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Api\GraphQL\Types\ModuleAccessType;
|
||||
use IPS\Application;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ModuleAccess query for GraphQL API
|
||||
*/
|
||||
class _ModuleAccess
|
||||
class ModuleAccess
|
||||
{
|
||||
public function __construct($app)
|
||||
protected ?Application $app = null;
|
||||
|
||||
public function __construct( Application $app )
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
@@ -33,7 +37,7 @@ class _ModuleAccess
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns module view permissions";
|
||||
public static string $description = "Returns module view permissions";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
@@ -46,7 +50,7 @@ class _ModuleAccess
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
public function type() : ModuleAccessType
|
||||
{
|
||||
return TypeRegistry::moduleAccess();
|
||||
}
|
||||
@@ -54,12 +58,12 @@ class _ModuleAccess
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Application
|
||||
* @param mixed $val Value passed into this resolver
|
||||
* @param array $args Arguments
|
||||
* @param array $context Context values
|
||||
* @return Application
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
public function resolve( mixed $val, array $args, array $context ) : Application
|
||||
{
|
||||
return $this->app;
|
||||
}
|
||||
|
||||
@@ -11,28 +11,30 @@
|
||||
namespace IPS\Api\GraphQL;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Error\ClientAware;
|
||||
use function defined;
|
||||
|
||||
/**
|
||||
* API Exception
|
||||
*/
|
||||
class _SafeException extends \Exception implements ClientAware
|
||||
class SafeException extends Exception implements ClientAware
|
||||
{
|
||||
/**
|
||||
* @brief Exception code
|
||||
*/
|
||||
public $exceptionCode;
|
||||
public ?string $exceptionCode = null;
|
||||
|
||||
/**
|
||||
* @brief OAUth Error
|
||||
*/
|
||||
public $oauthError;
|
||||
public ?string $oauthError = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -40,21 +42,20 @@ class _SafeException extends \Exception implements ClientAware
|
||||
* @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 )
|
||||
public function __construct( string $message, string $code, int $httpCode )
|
||||
{
|
||||
$this->exceptionCode = $code;
|
||||
return parent::__construct( $message, $httpCode );
|
||||
parent::__construct( $message, $httpCode );
|
||||
}
|
||||
|
||||
public function getCategory()
|
||||
public function getCategory() : string
|
||||
{
|
||||
return 'clienterror';
|
||||
}
|
||||
|
||||
public function isClientSafe()
|
||||
public function isClientSafe() : bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,33 +10,49 @@
|
||||
*/
|
||||
|
||||
namespace IPS\Api\GraphQL;
|
||||
use GraphQL\Type\Definition\BooleanType;
|
||||
use GraphQL\Type\Definition\FloatType;
|
||||
use GraphQL\Type\Definition\IDType;
|
||||
use GraphQL\Type\Definition\IntType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\StringType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use IPS\Api\GraphQL\Types\FollowType;
|
||||
use IPS\Api\GraphQL\Types\ImageType;
|
||||
use IPS\Api\GraphQL\Types\ItemStateType;
|
||||
use IPS\Api\GraphQL\Types\ModuleAccessType;
|
||||
use IPS\Api\GraphQL\Types\MutationType;
|
||||
use IPS\Api\GraphQL\Types\QueryType;
|
||||
use IPS\Api\GraphQL\Types\ReputationType;
|
||||
use IPS\Api\GraphQL\Types\RichTextType;
|
||||
use IPS\Api\GraphQL\Types\UrlType;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type registry
|
||||
*/
|
||||
class _TypeRegistry
|
||||
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;
|
||||
protected static ?QueryType $query = null;
|
||||
protected static ?MutationType $mutation = null;
|
||||
protected static ?ItemStateType $itemState = null;
|
||||
protected static ?ImageType $image = null;
|
||||
protected static ?ReputationType $reputation = null;
|
||||
protected static ?RichTextType $richText = null;
|
||||
protected static ?UrlType $url = null;
|
||||
protected static ?FollowType $follow = null;
|
||||
protected static ?ModuleAccessType $moduleAccess = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -47,134 +63,136 @@ class _TypeRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\QueryType
|
||||
* @return QueryType
|
||||
*/
|
||||
public static function query(): \IPS\Api\GraphQL\Types\QueryType
|
||||
public static function query(): QueryType
|
||||
{
|
||||
return self::$query ?: (self::$query = new \IPS\Api\GraphQL\Types\QueryType());
|
||||
return self::$query ?: (self::$query = new QueryType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\MutationType
|
||||
* @return MutationType
|
||||
*/
|
||||
public static function mutation(): \IPS\Api\GraphQL\Types\MutationType
|
||||
public static function mutation(): MutationType
|
||||
{
|
||||
return self::$mutation ?: (self::$mutation = new \IPS\Api\GraphQL\Types\MutationType());
|
||||
return self::$mutation ?: (self::$mutation = new MutationType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\ItemStateType
|
||||
* @return ItemStateType
|
||||
*/
|
||||
public static function itemState(): \IPS\Api\GraphQL\Types\ItemStateType
|
||||
public static function itemState(): ItemStateType
|
||||
{
|
||||
return self::$itemState ?: (self::$itemState = new \IPS\Api\GraphQL\Types\ItemStateType());
|
||||
return self::$itemState ?: (self::$itemState = new ItemStateType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImageType
|
||||
*/
|
||||
public static function image(): \IPS\Api\GraphQL\Types\ImageType
|
||||
public static function image(): ImageType
|
||||
{
|
||||
return self::$image ?: (self::$image = new \IPS\Api\GraphQL\Types\ImageType());
|
||||
return self::$image ?: (self::$image = new ImageType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\ReputationType
|
||||
* @return ReputationType
|
||||
*/
|
||||
public static function reputation(): \IPS\Api\GraphQL\Types\ReputationType
|
||||
public static function reputation(): ReputationType
|
||||
{
|
||||
return self::$reputation ?: (self::$reputation = new \IPS\Api\GraphQL\Types\ReputationType());
|
||||
return self::$reputation ?: (self::$reputation = new ReputationType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\RichTextType
|
||||
* @return RichTextType
|
||||
*/
|
||||
public static function richText(): \IPS\Api\GraphQL\Types\RichTextType
|
||||
public static function richText(): RichTextType
|
||||
{
|
||||
return self::$richText ?: (self::$richText = new \IPS\Api\GraphQL\Types\RichTextType());
|
||||
return self::$richText ?: (self::$richText = new RichTextType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\UrlType
|
||||
* @return UrlType
|
||||
*/
|
||||
public static function url(): \IPS\Api\GraphQL\Types\UrlType
|
||||
public static function url(): UrlType
|
||||
{
|
||||
return self::$url ?: (self::$url = new \IPS\Api\GraphQL\Types\UrlType());
|
||||
return self::$url ?: (self::$url = new UrlType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\FollowType
|
||||
* @return FollowType
|
||||
*/
|
||||
public static function follow(): \IPS\Api\GraphQL\Types\FollowType
|
||||
public static function follow(): FollowType
|
||||
{
|
||||
return self::$follow ?: (self::$follow = new \IPS\Api\GraphQL\Types\FollowType());
|
||||
return self::$follow ?: (self::$follow = new FollowType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \IPS\Api\GraphQL\Types\ModuleAccessType
|
||||
* @return ModuleAccessType
|
||||
*/
|
||||
public static function moduleAccess(): \IPS\Api\GraphQL\Types\ModuleAccessType
|
||||
public static function moduleAccess(): ModuleAccessType
|
||||
{
|
||||
return self::$moduleAccess ?: (self::$moduleAccess = new \IPS\Api\GraphQL\Types\ModuleAccessType());
|
||||
return self::$moduleAccess ?: (self::$moduleAccess = new ModuleAccessType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\IDType
|
||||
* @return ScalarType
|
||||
*/
|
||||
public static function id(): \GraphQL\Type\Definition\IDType
|
||||
public static function id(): IDType
|
||||
{
|
||||
return Type::id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\StringType
|
||||
* @return ScalarType
|
||||
*/
|
||||
public static function string(): \GraphQL\Type\Definition\StringType
|
||||
public static function string(): StringType
|
||||
{
|
||||
return Type::string();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\IntType
|
||||
* @return ScalarType
|
||||
*/
|
||||
public static function int(): \GraphQL\Type\Definition\IntType
|
||||
public static function int(): IntType
|
||||
{
|
||||
return Type::int();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\FloatType
|
||||
* @return ScalarType
|
||||
*/
|
||||
public static function float(): \GraphQL\Type\Definition\FloatType
|
||||
public static function float(): FloatType
|
||||
{
|
||||
return Type::float();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\BooleanType
|
||||
* @return ScalarType
|
||||
*/
|
||||
public static function boolean(): \GraphQL\Type\Definition\BooleanType
|
||||
public static function boolean(): BooleanType
|
||||
{
|
||||
return Type::boolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\ListOfType
|
||||
*/
|
||||
public static function listOf($type): \GraphQL\Type\Definition\ListOfType
|
||||
* @param mixed $type
|
||||
* @return ListOfType
|
||||
*/
|
||||
public static function listOf( mixed $type): ListOfType
|
||||
{
|
||||
return new ListOfType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GraphQL\Type\Definition\EnumType
|
||||
*/
|
||||
public static function eNum($config): \GraphQL\Type\Definition\EnumType
|
||||
* @param array $config
|
||||
* @return EnumType
|
||||
*/
|
||||
public static function eNum( array $config): EnumType
|
||||
{
|
||||
return new EnumType($config);
|
||||
}
|
||||
|
||||
public static function inputObjectType($config): \GraphQL\Type\Definition\InputObjectType
|
||||
public static function inputObjectType( array $config): InputObjectType
|
||||
{
|
||||
return new InputObjectType($config);
|
||||
}
|
||||
@@ -183,7 +201,7 @@ class _TypeRegistry
|
||||
* @param Type $type
|
||||
* @return NonNull
|
||||
*/
|
||||
public static function nonNull($type): \GraphQL\Type\Definition\NonNull
|
||||
public static function nonNull( mixed $type): NonNull
|
||||
{
|
||||
return new NonNull($type);
|
||||
}
|
||||
|
||||
@@ -12,23 +12,27 @@
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Content;
|
||||
use IPS\Db;
|
||||
use IPS\Member;
|
||||
use UnderflowException;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* FollowType for GraphQL API
|
||||
*/
|
||||
class _FollowType extends ObjectType
|
||||
class FollowType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get root type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -47,13 +51,13 @@ class _FollowType extends ObjectType
|
||||
'followID' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'resolve' => function ($followData) {
|
||||
return md5( $followData['app'] . ';' . $followData['area'] . ';' . $followData['id'] . ';' . \IPS\Member::loggedIn()->member_id );
|
||||
return md5( $followData['app'] . ';' . $followData['area'] . ';' . $followData['id'] . ';' . Member::loggedIn()->member_id );
|
||||
}
|
||||
],
|
||||
'isFollowing' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function ($followData) {
|
||||
return \IPS\Member::loggedIn()->following( $followData['app'], $followData['area'], $followData['id'] );
|
||||
return Member::loggedIn()->following( $followData['app'], $followData['area'], $followData['id'] );
|
||||
}
|
||||
],
|
||||
'followType' => [
|
||||
@@ -62,18 +66,18 @@ class _FollowType extends ObjectType
|
||||
'values' => ['NOT_FOLLOWING', 'PUBLIC', 'ANONYMOUS']
|
||||
]),
|
||||
'resolve' => function ($followData) {
|
||||
if( !\IPS\Member::loggedIn()->following( $followData['app'], $followData['area'], $followData['id'] ) )
|
||||
if( !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();
|
||||
$current = 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'], Member::loggedIn()->member_id ), NULL, array() )->first();
|
||||
|
||||
return $current['follow_is_anon'] ? 'ANONYMOUS' : 'PUBLIC';
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
return 'NOT_FOLLOWING';
|
||||
}
|
||||
@@ -107,11 +111,11 @@ class _FollowType extends ObjectType
|
||||
}
|
||||
elseif( isset( $followData['member'] ) )
|
||||
{
|
||||
return $followData['member']->followersCount( \IPS\Member::FOLLOW_ANONYMOUS );
|
||||
return $followData['member']->followersCount( Content::FOLLOW_ANONYMOUS );
|
||||
}
|
||||
else
|
||||
{
|
||||
return $followData['item']->followersCount( $followData['itemClass']::FOLLOW_ANONYMOUS );
|
||||
return $followData['item']->followersCount( Content::FOLLOW_ANONYMOUS );
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -138,7 +142,7 @@ class _FollowType extends ObjectType
|
||||
}
|
||||
elseif( isset( $followData['member'] ) )
|
||||
{
|
||||
$_followers = $followData['member']->followers( \IPS\Member::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( $offset, $limit ), 'name' );
|
||||
$_followers = $followData['member']->followers( Content::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( $offset, $limit ), 'name' );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -155,7 +159,7 @@ class _FollowType extends ObjectType
|
||||
|
||||
foreach( $_followers as $followerRow )
|
||||
{
|
||||
$followers[] = \IPS\Member::load( $followerRow['follow_member_id'] );
|
||||
$followers[] = Member::load( $followerRow['follow_member_id'] );
|
||||
}
|
||||
|
||||
return $followers;
|
||||
@@ -176,7 +180,7 @@ class _FollowType extends ObjectType
|
||||
]) ),
|
||||
'resolve' => function ($followData, $args) {
|
||||
// No options if we're a guest
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
if( !Member::loggedIn()->member_id )
|
||||
{
|
||||
return array();
|
||||
}
|
||||
@@ -184,9 +188,9 @@ class _FollowType extends ObjectType
|
||||
/* 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();
|
||||
$current = 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'], Member::loggedIn()->member_id ), NULL, array() )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
$current = FALSE;
|
||||
}
|
||||
@@ -205,8 +209,8 @@ class _FollowType extends ObjectType
|
||||
$followType = 'new_comment';
|
||||
}
|
||||
|
||||
$notificationConfiguration = \IPS\Member::loggedIn()->notificationsConfiguration();
|
||||
$notificationConfiguration = isset( $notificationConfiguration[ $followType ] ) ? $notificationConfiguration[ $followType ] : array();
|
||||
$notificationConfiguration = Member::loggedIn()->notificationsConfiguration();
|
||||
$notificationConfiguration = $notificationConfiguration[$followType] ?? array();
|
||||
|
||||
// Now figure out which options we can have
|
||||
$options = array( array(
|
||||
|
||||
@@ -12,23 +12,23 @@
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ImageType for GraphQL API
|
||||
*/
|
||||
class _ImageType extends ObjectType
|
||||
class ImageType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -12,23 +12,23 @@
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ItemStateType for GraphQL API
|
||||
*/
|
||||
class _ItemStateType extends InputObjectType
|
||||
class ItemStateType extends InputObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -10,25 +10,28 @@
|
||||
*/
|
||||
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use Exception;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Member;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ModuleAccessType for GraphQL API
|
||||
*/
|
||||
class _ModuleAccessType extends ObjectType
|
||||
class ModuleAccessType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -47,9 +50,9 @@ class _ModuleAccessType extends ObjectType
|
||||
$canAccess = FALSE;
|
||||
try
|
||||
{
|
||||
$canAccess = \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( $app, $args['module'], 'front' ) );
|
||||
$canAccess = Member::loggedIn()->canAccessModule( Module::get( $app, $args['module'], 'front' ) );
|
||||
}
|
||||
catch (\Exception $e)
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Just fall through so we return false
|
||||
}
|
||||
|
||||
@@ -11,19 +11,21 @@
|
||||
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Api\Exception;
|
||||
use IPS\Application;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Exception
|
||||
*/
|
||||
class _MutationType extends ObjectType
|
||||
class MutationType extends ObjectType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
@@ -32,7 +34,7 @@ class _MutationType extends ObjectType
|
||||
'fields' => function () {
|
||||
$_fields = array();
|
||||
|
||||
foreach( \IPS\Application::enabledApplications() as $key => $app )
|
||||
foreach( Application::enabledApplications() as $key => $app )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -55,7 +57,7 @@ class _MutationType extends ObjectType
|
||||
);
|
||||
}
|
||||
|
||||
$_fields[ 'mutate' . ucfirst($app->directory) ] = new \GraphQL\Type\Definition\ObjectType( array(
|
||||
$_fields[ 'mutate' . ucfirst($app->directory) ] = new ObjectType( array(
|
||||
'name' => 'mutate_' . ucfirst($app->directory),
|
||||
'fields' => $mutationFields
|
||||
) );
|
||||
@@ -87,7 +89,7 @@ class _MutationType extends ObjectType
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_RESOLVE', '3S291/2_graphl', 405 );
|
||||
throw new Exception( 'NO_RESOLVE', '3S291/2_graphl', 405 );
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -11,19 +11,22 @@
|
||||
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Api\Exception;
|
||||
use IPS\Api\GraphQL\AppQueries;
|
||||
use IPS\Application;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Exception
|
||||
*/
|
||||
class _QueryType extends ObjectType
|
||||
class QueryType extends ObjectType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
@@ -32,11 +35,11 @@ class _QueryType extends ObjectType
|
||||
'fields' => function () {
|
||||
$_fields = array();
|
||||
|
||||
foreach( \IPS\Application::enabledApplications() as $key => $app )
|
||||
foreach( Application::enabledApplications() as $key => $app )
|
||||
{
|
||||
try
|
||||
{
|
||||
$globalAppQueries = \IPS\Api\GraphQL\AppQueries::queries($app->directory);
|
||||
$globalAppQueries = AppQueries::queries($app->directory);
|
||||
$appQueryClass = "\\IPS\\" . $app->directory . "\\api\\GraphQL\\Query";
|
||||
|
||||
if( !class_exists( $appQueryClass ) )
|
||||
@@ -67,7 +70,7 @@ class _QueryType extends ObjectType
|
||||
);
|
||||
}
|
||||
|
||||
$_fields[ $app->directory ] = new \GraphQL\Type\Definition\ObjectType( array(
|
||||
$_fields[ $app->directory ] = new ObjectType( array(
|
||||
'name' => $app->directory,
|
||||
'fields' => $queryFields
|
||||
) );
|
||||
@@ -84,7 +87,7 @@ class _QueryType extends ObjectType
|
||||
'resolveField' => function ($val, $args, $context, $info) {
|
||||
try
|
||||
{
|
||||
$globalAppQueries = \IPS\Api\GraphQL\AppQueries::queries($info->fieldName);
|
||||
$globalAppQueries = AppQueries::queries($info->fieldName);
|
||||
$appQueryClass = '\\IPS\\' . $info->fieldName . '\\api\\GraphQL\\Query';
|
||||
$queryResolvers = array();
|
||||
$queryTypes = $appQueryClass::queries();
|
||||
@@ -111,7 +114,7 @@ class _QueryType extends ObjectType
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_RESOLVE', '3S291/2', 405 );
|
||||
throw new Exception( 'NO_RESOLVE', '3S291/2', 405 );
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -12,23 +12,29 @@
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Content;
|
||||
use IPS\Db;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use function defined;
|
||||
use function get_class;
|
||||
use function intval;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ReputationType for GraphQL API
|
||||
*/
|
||||
class _ReputationType extends ObjectType
|
||||
class ReputationType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get root type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -39,7 +45,7 @@ class _ReputationType extends ObjectType
|
||||
'reactionCount' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'resolve' => function ($content, $args) {
|
||||
if( !\IPS\Settings::i()->reputation_enabled )
|
||||
if( !Settings::i()->reputation_enabled )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -50,13 +56,13 @@ class _ReputationType extends ObjectType
|
||||
'canViewReps' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function ($content, $args) {
|
||||
return \IPS\Settings::i()->reputation_enabled && \IPS\Member::loggedIn()->group['gbw_view_reps'];
|
||||
return Settings::i()->reputation_enabled && Member::loggedIn()->group['gbw_view_reps'];
|
||||
}
|
||||
],
|
||||
'canReact' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function ($content, $args) {
|
||||
return \IPS\Settings::i()->reputation_enabled && $content->canReact();
|
||||
return Settings::i()->reputation_enabled && $content->canReact();
|
||||
}
|
||||
],
|
||||
'hasReacted' => [
|
||||
@@ -65,7 +71,7 @@ class _ReputationType extends ObjectType
|
||||
'isLikeMode' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function ($content, $args) {
|
||||
return \IPS\Settings::i()->reputation_enabled && \IPS\Content\Reaction::isLikeMode();
|
||||
return Settings::i()->reputation_enabled && Content\Reaction::isLikeMode();
|
||||
}
|
||||
],
|
||||
'givenReaction' => [
|
||||
@@ -80,7 +86,7 @@ class _ReputationType extends ObjectType
|
||||
'reactions' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::contentReaction() ),
|
||||
'resolve' => function ($content) {
|
||||
if( !\IPS\Settings::i()->reputation_enabled )
|
||||
if( !Settings::i()->reputation_enabled )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -90,7 +96,7 @@ class _ReputationType extends ObjectType
|
||||
|
||||
foreach( $content->reactBlurb() as $key => $count )
|
||||
{
|
||||
$return[] = array( 'id' => md5( \get_class( $content ) . '-' . $content->$idColumn ) . '-' . $key, 'reactionId' => $key, 'count' => $count );
|
||||
$return[] = array( 'id' => md5( get_class( $content ) . '-' . $content->$idColumn ) . '-' . $key, 'reactionId' => $key, 'count' => $count );
|
||||
}
|
||||
|
||||
return $return;
|
||||
@@ -110,7 +116,7 @@ class _ReputationType extends ObjectType
|
||||
]
|
||||
],
|
||||
'resolve' => function ($content, $args) {
|
||||
if( !\IPS\Settings::i()->reputation_enabled || !\IPS\Member::loggedIn()->group['gbw_view_reps'] )
|
||||
if( !Settings::i()->reputation_enabled || !Member::loggedIn()->group['gbw_view_reps'] )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -122,12 +128,12 @@ class _ReputationType extends ObjectType
|
||||
|
||||
if( isset( $args['id'] ) )
|
||||
{
|
||||
$reaction = \intval( $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 )
|
||||
foreach( 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'] );
|
||||
$members[] = Member::load( $reaction['member_id'] );
|
||||
}
|
||||
|
||||
return $members;
|
||||
@@ -135,12 +141,12 @@ class _ReputationType extends ObjectType
|
||||
]
|
||||
],
|
||||
'resolveField' => function ($content, $args, $context, $info) {
|
||||
if( !\IPS\Settings::i()->reputation_enabled )
|
||||
if( !Settings::i()->reputation_enabled )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$enabledReactions = \IPS\Content\Reaction::enabledReactions();
|
||||
$enabledReactions = Content\Reaction::enabledReactions();
|
||||
$defaultReaction = reset( $enabledReactions );
|
||||
$reacted = $content->reacted();
|
||||
|
||||
@@ -148,25 +154,25 @@ class _ReputationType extends ObjectType
|
||||
{
|
||||
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;
|
||||
$reaction = $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();
|
||||
|
||||
@@ -179,8 +185,10 @@ class _ReputationType extends ObjectType
|
||||
}
|
||||
|
||||
return $reactions;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -12,23 +12,25 @@
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Output;
|
||||
use IPS\Text\Parser;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* RichTextType for GraphQL API
|
||||
*/
|
||||
class _RichTextType extends ObjectType
|
||||
class RichTextType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -54,7 +56,7 @@ class _RichTextType extends ObjectType
|
||||
'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]' ) );
|
||||
$string = 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 );
|
||||
@@ -83,20 +85,9 @@ class _RichTextType extends ObjectType
|
||||
'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
|
||||
]
|
||||
],
|
||||
'args' => [ ],
|
||||
'resolve' => function ($string, $args) {
|
||||
if( $args['removeLazyLoad'] )
|
||||
{
|
||||
$string = \IPS\Text\Parser::removeLazyLoad( $string );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->parseFileObjectUrls( $string );
|
||||
Output::i()->parseFileObjectUrls( $string );
|
||||
return $string;
|
||||
}
|
||||
]
|
||||
|
||||
@@ -12,23 +12,25 @@
|
||||
namespace IPS\Api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
use IPS\Http\Url;
|
||||
use function defined;
|
||||
use function in_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* URLType for GraphQL API
|
||||
*/
|
||||
class _UrlType extends ObjectType
|
||||
class UrlType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -68,9 +70,9 @@ class _UrlType extends ObjectType
|
||||
}
|
||||
])),
|
||||
'resolve' => function ($url) {
|
||||
if( !( $url instanceof \IPS\Http\Url ) )
|
||||
if( !( $url instanceof Url ) )
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $url );
|
||||
$url = Url::createFromString( $url );
|
||||
}
|
||||
|
||||
$pieces = array();
|
||||
@@ -89,13 +91,13 @@ class _UrlType extends ObjectType
|
||||
];
|
||||
},
|
||||
'resolveField' => function ($url, $args, $context, $info) {
|
||||
if( !( $url instanceof \IPS\Http\Url ) )
|
||||
if( !( $url instanceof Url ) )
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $url );
|
||||
$url = Url::createFromString( $url );
|
||||
}
|
||||
|
||||
$urlPieces = $url->hiddenQueryString;
|
||||
if( \in_array( $info->fieldName, array('app', 'module', 'controller') ) )
|
||||
if( in_array( $info->fieldName, array('app', 'module', 'controller') ) )
|
||||
{
|
||||
return $urlPieces[ $info->fieldName ];
|
||||
}
|
||||
|
||||
+49
-31
@@ -11,36 +11,53 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Custom;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Stack;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Request;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
use function intval;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Key
|
||||
*/
|
||||
class _Key extends \IPS\Node\Model
|
||||
class Key extends Model
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
protected static array $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_api_keys';
|
||||
public static ?string $databaseTable = 'core_api_keys';
|
||||
|
||||
/**
|
||||
* @brief Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'api_';
|
||||
public static string $databasePrefix = 'api_';
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'api_keys';
|
||||
public static string $nodeTitle = 'api_keys';
|
||||
|
||||
/**
|
||||
* @brief [Node] ACP Restrictions
|
||||
@@ -58,7 +75,7 @@ class _Key extends \IPS\Node\Model
|
||||
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
|
||||
* @endcode
|
||||
*/
|
||||
protected static $restrictions = array(
|
||||
protected static ?array $restrictions = array(
|
||||
'app' => 'core',
|
||||
'module' => 'applications',
|
||||
'prefix' => 'api_',
|
||||
@@ -67,16 +84,16 @@ class _Key extends \IPS\Node\Model
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
|
||||
*/
|
||||
public static $titleLangPrefix = 'core_api_name_';
|
||||
public static ?string $titleLangPrefix = 'core_api_name_';
|
||||
|
||||
/**
|
||||
* Get description
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function get__description()
|
||||
public function get__description(): ?string
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate('api')->apiKey( $this->id );
|
||||
return Theme::i()->getTemplate('api')->apiKey( $this->id );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +104,7 @@ class _Key extends \IPS\Node\Model
|
||||
* @param string $method Method
|
||||
* @return bool
|
||||
*/
|
||||
public function canAccess( $app, $controller, $method )
|
||||
public function canAccess( string $app, string $controller, string $method ) : bool
|
||||
{
|
||||
$permissions = json_decode( $this->permissions, TRUE );
|
||||
return isset( $permissions["{$app}/{$controller}/{$method}"] ) and $permissions["{$app}/{$controller}/{$method}"]['access'] == TRUE;
|
||||
@@ -101,7 +118,7 @@ class _Key extends \IPS\Node\Model
|
||||
* @param string $method Method
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldLog( $app, $controller, $method )
|
||||
public function shouldLog( string $app, string $controller, string $method ) : bool
|
||||
{
|
||||
$permissions = json_decode( $this->permissions, TRUE );
|
||||
return isset( $permissions["{$app}/{$controller}/{$method}"] ) and isset( $permissions["{$app}/{$controller}/{$method}"]['log'] ) and $permissions["{$app}/{$controller}/{$method}"]['log'] == TRUE;
|
||||
@@ -110,25 +127,25 @@ class _Key extends \IPS\Node\Model
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
*
|
||||
* @param \IPS\Helpers\Form $form The form
|
||||
* @param Form $form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( &$form )
|
||||
public function form( Form &$form ) : void
|
||||
{
|
||||
/* API Key */
|
||||
$form->add( new \IPS\Helpers\Form\Custom( 'api_id', $this->id ?: md5( mt_rand() ), TRUE, array(
|
||||
$form->add( new Custom( 'api_id', $this->id ?: md5( mt_rand() ), TRUE, array(
|
||||
'getHtml' => function( $field )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate('api')->apiKeyField( $field->name, $field->value );
|
||||
return Theme::i()->getTemplate('api')->apiKeyField( $field->name, $field->value );
|
||||
},
|
||||
'disableCopy' => TRUE
|
||||
) ) );
|
||||
|
||||
/* Description */
|
||||
$form->add( new \IPS\Helpers\Form\Translatable( 'api_name', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? "core_api_name_{$this->id}" : NULL ) ) ) );
|
||||
$form->add( new Translatable( 'api_name', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? "core_api_name_{$this->id}" : NULL ) ) ) );
|
||||
|
||||
/* Allowed IPs */
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'api_enable_ip_restriction', \intval( $this->allowed_ips !== NULL ), FALSE, array(
|
||||
$form->add( new Radio( 'api_enable_ip_restriction', intval( $this->allowed_ips !== NULL ), FALSE, array(
|
||||
'options' => array(
|
||||
0 => 'api_enable_ip_restriction_off',
|
||||
1 => 'api_enable_ip_restriction_on',
|
||||
@@ -138,37 +155,38 @@ class _Key extends \IPS\Node\Model
|
||||
)
|
||||
) ) );
|
||||
|
||||
if ( \IPS\Request::i()->isCgi() )
|
||||
if ( Request::i()->isCgi() )
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['api_enable_ip_restriction_warning'] = \IPS\Member::loggedIn()->language()->addToStack('api_enable_ip_restriction__warning');
|
||||
Member::loggedIn()->language()->words['api_enable_ip_restriction_warning'] = Member::loggedIn()->language()->addToStack('api_enable_ip_restriction__warning');
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Stack( 'api_allowed_ips', explode( ',', $this->allowed_ips ), NULL, array(), function( $val )
|
||||
$form->add( new Stack( 'api_allowed_ips', explode( ',', $this->allowed_ips ), NULL, array(), function( $val )
|
||||
{
|
||||
if ( $val )
|
||||
{
|
||||
foreach ( array_filter( \is_array( $val ) ? $val : array( $val ) ) as $ip )
|
||||
foreach ( array_filter( is_array( $val ) ? $val : array( $val ) ) as $ip )
|
||||
{
|
||||
if ( filter_var( $ip, FILTER_VALIDATE_IP ) === FALSE )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'api_allowed_ips_err', FALSE, array( 'sprintf' => array( $ip ) ) ) );
|
||||
throw new DomainException( Member::loggedIn()->language()->addToStack( 'api_allowed_ips_err', FALSE, array( 'sprintf' => array( $ip ) ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}, NULL, NULL, 'api_allowed_ips' ) );
|
||||
|
||||
/* Permissions */
|
||||
$form->add( new \IPS\Helpers\Form\Custom( 'api_permissions', $this->permissions ? json_decode( $this->permissions, TRUE ) : array(), FALSE, array(
|
||||
$form->add( new Custom( 'api_permissions', $this->permissions ? json_decode( $this->permissions, TRUE ) : array(), FALSE, array(
|
||||
'rowHtml' => function( $field )
|
||||
{
|
||||
$endpoints = \IPS\Api\Controller::getAllEndpoints('client');
|
||||
$endpoints = Controller::getAllEndpoints('client');
|
||||
$endpointTree = [];
|
||||
foreach ( $endpoints as $key => $endpoint )
|
||||
{
|
||||
$pieces = explode('/', $key);
|
||||
$endpointTree[ $pieces[0] ][ $pieces[1] ][ $key ] = $endpoint;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'api' )->permissionsField( $endpointTree, $field->name, $field->value );
|
||||
return Theme::i()->getTemplate( 'api' )->permissionsField( $endpointTree, $field->name, $field->value );
|
||||
}
|
||||
) ) );
|
||||
|
||||
@@ -180,7 +198,7 @@ class _Key extends \IPS\Node\Model
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
$values['api_permissions'] = json_encode( $values['api_permissions'] );
|
||||
|
||||
@@ -196,7 +214,7 @@ class _Key extends \IPS\Node\Model
|
||||
$this->save();
|
||||
}
|
||||
|
||||
\IPS\Lang::saveCustom( 'core', "core_api_name_{$this->id}", $values['api_name'] );
|
||||
Lang::saveCustom( 'core', "core_api_name_{$this->id}", $values['api_name'] );
|
||||
unset( $values['api_name'] );
|
||||
|
||||
return $values;
|
||||
@@ -221,6 +239,6 @@ class _Key extends \IPS\Node\Model
|
||||
$this->save();
|
||||
|
||||
/* ...and language */
|
||||
\IPS\Lang::saveCustom( 'core', 'core_api_name_' . $this->id, iterator_to_array( \IPS\Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', 'core_api_name_' . $oldId ) )->setKeyField( 'lang_id' )->setValueField( 'word_custom' ) ) );
|
||||
Lang::saveCustom( 'core', 'core_api_name_' . $this->id, iterator_to_array( Db::i()->select( 'word_custom, lang_id', 'core_sys_lang_words', array( 'word_key=?', 'core_api_name_' . $oldId ) )->setKeyField( 'lang_id' )->setValueField( 'word_custom' ) ) );
|
||||
}
|
||||
}
|
||||
+174
-140
@@ -12,46 +12,73 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\CheckboxSet;
|
||||
use IPS\Helpers\Form\Custom;
|
||||
use IPS\Helpers\Form\Interval;
|
||||
use IPS\Helpers\Form\Matrix;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Stack;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Helpers\Form\Url as FormUrl;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Login;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Device;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use UnderflowException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function in_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth Client
|
||||
*/
|
||||
class _OAuthClient extends \IPS\Node\Model
|
||||
class OAuthClient extends Model
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
protected static array $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_oauth_clients';
|
||||
public static ?string $databaseTable = 'core_oauth_clients';
|
||||
|
||||
/**
|
||||
* @brief Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'oauth_';
|
||||
public static string $databasePrefix = 'oauth_';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] ID Database Column
|
||||
*/
|
||||
public static $databaseColumnId = 'client_id';
|
||||
public static string $databaseColumnId = 'client_id';
|
||||
|
||||
/**
|
||||
* @brief [Node] Enabled/Disabled Column
|
||||
*/
|
||||
public static $databaseColumnEnabledDisabled = 'enabled';
|
||||
public static ?string $databaseColumnEnabledDisabled = 'enabled';
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'oauth_clients';
|
||||
public static string $nodeTitle = 'oauth_clients';
|
||||
|
||||
/**
|
||||
* @brief [Node] ACP Restrictions
|
||||
@@ -69,7 +96,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
|
||||
* @endcode
|
||||
*/
|
||||
protected static $restrictions = array(
|
||||
protected static ?array $restrictions = array(
|
||||
'app' => 'core',
|
||||
'module' => 'applications',
|
||||
'prefix' => 'oauth_',
|
||||
@@ -78,14 +105,14 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
|
||||
*/
|
||||
public static $titleLangPrefix = 'core_oauth_client_';
|
||||
public static ?string $titleLangPrefix = 'core_oauth_client_';
|
||||
|
||||
/**
|
||||
* Set Default Values (overriding $defaultValues)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setDefaultValues()
|
||||
protected function setDefaultValues() : void
|
||||
{
|
||||
$this->access_token_length = 168;
|
||||
$this->prompt = 'reauthorize';
|
||||
@@ -97,10 +124,10 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
*
|
||||
* @param \IPS\Helpers\Form $form The form
|
||||
* @param Form $form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( &$form )
|
||||
public function form( Form &$form ) : void
|
||||
{
|
||||
$form->id = 'oauth';
|
||||
|
||||
@@ -119,9 +146,9 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
|
||||
$form->addTab('oauth_basic_settings');
|
||||
$form->addHeader('oauth_basic_settings');
|
||||
$form->add( new \IPS\Helpers\Form\Translatable( 'oauth_client_name', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->client_id ? "core_oauth_client_{$this->client_id}" : NULL ) ) ) );
|
||||
$form->add( new Translatable( 'oauth_client_name', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->client_id ? "core_oauth_client_{$this->client_id}" : NULL ) ) ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'oauth_client_type', $type, TRUE, array(
|
||||
$form->add( new Radio( 'oauth_client_type', $type, TRUE, array(
|
||||
'options' => array(
|
||||
'invision' => 'client_type_invision',
|
||||
'wordpress' => 'client_type_wordpress',
|
||||
@@ -136,7 +163,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
)
|
||||
) ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'oauth_api_access', $this->api_access ? $this->api_access : 'rest', TRUE, array(
|
||||
$form->add( new Radio( 'oauth_api_access', $this->api_access ? $this->api_access : 'rest', TRUE, array(
|
||||
'options' => array(
|
||||
'rest' => 'oauth_api_type_rest',
|
||||
'graphql' => 'oauth_api_type_graphql',
|
||||
@@ -149,13 +176,13 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
)
|
||||
), NULL, NULL, NULL, 'oauth_api_access' ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'oauth_invision_grant_type', $this->client_id ? $this->grant_types : 'authorization_code', NULL, array(
|
||||
$form->add( new Radio( 'oauth_invision_grant_type', $this->client_id ? $this->grant_types : 'authorization_code', NULL, array(
|
||||
'options' => array(
|
||||
'authorization_code' => 'invision_grant_type_server_authorization_code',
|
||||
'password' => 'invision_grant_type_server_password',
|
||||
),
|
||||
), NULL, NULL, NULL, 'oauth_grant_types_invision' ) );
|
||||
$confidentialGrant = new \IPS\Helpers\Form\CheckboxSet( 'oauth_grant_types_confidential', $this->client_id ? explode( ',', $this->grant_types ) : array( 'authorization_code' ), NULL, array(
|
||||
$confidentialGrant = new CheckboxSet( 'oauth_grant_types_confidential', $this->client_id ? explode( ',', $this->grant_types ) : array( 'authorization_code' ), NULL, array(
|
||||
'options' => array(
|
||||
'authorization_code' => 'grant_type_authorization_code',
|
||||
'implicit' => 'grant_type_implicit',
|
||||
@@ -166,13 +193,13 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
'authorization_code' => array( 'oauth_pkce', 'oauth_use_refresh_tokens' )
|
||||
)
|
||||
), function( $val ) {
|
||||
if ( !$val and \IPS\Request::i()->oauth_client_type === 'confidential' ) {
|
||||
throw new \DomainException('form_required');
|
||||
if ( !$val and Request::i()->oauth_client_type === 'confidential' ) {
|
||||
throw new DomainException('form_required');
|
||||
}
|
||||
}, NULL, NULL, 'oauth_grant_types_confidential' );
|
||||
$confidentialGrant->label = \IPS\Member::loggedIn()->language()->addToStack('oauth_grant_types');
|
||||
$confidentialGrant->label = Member::loggedIn()->language()->addToStack('oauth_grant_types');
|
||||
$form->add( $confidentialGrant );
|
||||
$publicGrant = new \IPS\Helpers\Form\CheckboxSet( 'oauth_grant_types_public', $this->client_id ? explode( ',', $this->grant_types ) : array( 'implicit' ), NULL, array(
|
||||
$publicGrant = new CheckboxSet( 'oauth_grant_types_public', $this->client_id ? explode( ',', $this->grant_types ) : array( 'implicit' ), NULL, array(
|
||||
'options' => array(
|
||||
'authorization_code' => 'grant_type_authorization_code',
|
||||
'implicit' => 'grant_type_implicit',
|
||||
@@ -182,85 +209,86 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
'authorization_code' => array( 'oauth_pkce', 'oauth_use_refresh_tokens' )
|
||||
)
|
||||
), function( $val ) {
|
||||
if ( !$val and \IPS\Request::i()->oauth_client_type === 'public' ) {
|
||||
throw new \DomainException('form_required');
|
||||
if ( !$val and Request::i()->oauth_client_type === 'public' ) {
|
||||
throw new DomainException('form_required');
|
||||
}
|
||||
}, NULL, NULL, 'oauth_grant_types_public' );
|
||||
$publicGrant->label = \IPS\Member::loggedIn()->language()->addToStack('oauth_grant_types');
|
||||
$publicGrant->label = Member::loggedIn()->language()->addToStack('oauth_grant_types');
|
||||
$form->add( $publicGrant );
|
||||
$redirectUris = json_decode( $this->redirect_uris, TRUE );
|
||||
$form->add( new \IPS\Helpers\Form\Url( 'oauth_invision_endpoint', isset( $redirectUris[0] ) ? preg_replace( '#/oauth/callback/$#i', '/', $redirectUris[0] ) : NULL, NULL, array( 'placeholder' => 'https://othercommunity.example.com/', 'allowedProtocols' => NULL ), function( $val ) {
|
||||
if ( !$val and \IPS\Request::i()->oauth_client_type == 'invision' ) {
|
||||
throw new \DomainException('form_required');
|
||||
$form->add( new FormUrl( 'oauth_invision_endpoint', isset( $redirectUris[0] ) ? preg_replace( '#/oauth/callback/$#i', '/', $redirectUris[0] ) : NULL, NULL, array( 'placeholder' => 'https://othercommunity.example.com/', 'allowedProtocols' => NULL ), function( $val ) {
|
||||
if ( !$val and Request::i()->oauth_client_type == 'invision' ) {
|
||||
throw new DomainException('form_required');
|
||||
}
|
||||
if ( $val and $val instanceof \IPS\Http\Url and $val->data[ \IPS\Http\Url::COMPONENT_FRAGMENT ] ) {
|
||||
throw new \DomainException('oauth_redirect_uris_no_fragment');
|
||||
if ( $val and $val instanceof Url and $val->data[ Url::COMPONENT_FRAGMENT ] ) {
|
||||
throw new DomainException('oauth_redirect_uris_no_fragment');
|
||||
}
|
||||
if ( $val and rtrim( (string) $val, '/' ) === rtrim( \IPS\Settings::i()->base_url, '/' ) ) {
|
||||
throw new \DomainException('oauth_invision_endpoint_internal');
|
||||
if ( $val and rtrim( (string) $val, '/' ) === rtrim( Settings::i()->base_url, '/' ) ) {
|
||||
throw new DomainException('oauth_invision_endpoint_internal');
|
||||
}
|
||||
}, NULL, NULL, 'oauth_invision_endpoint' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Url( 'oauth_wordpress_endpoint', isset( $redirectUris[0] ) ? $redirectUris[0] : NULL, NULL, array( 'placeholder' => 'https://wordpress.example.com/', 'allowedProtocols' => NULL ), function( $val ) {
|
||||
if ( !$val and \IPS\Request::i()->oauth_client_type == 'wordpress' ) {
|
||||
throw new \DomainException('form_required');
|
||||
$form->add( new FormUrl( 'oauth_wordpress_endpoint', $redirectUris[0] ?? NULL, NULL, array( 'placeholder' => 'https://wordpress.example.com/', 'allowedProtocols' => NULL ), function( $val ) {
|
||||
if ( !$val and Request::i()->oauth_client_type == 'wordpress' ) {
|
||||
throw new DomainException('form_required');
|
||||
}
|
||||
if ( $val and $val instanceof \IPS\Http\Url and $val->data[ \IPS\Http\Url::COMPONENT_FRAGMENT ] ) {
|
||||
throw new \DomainException('oauth_redirect_uris_no_fragment');
|
||||
if ( $val and $val instanceof Url and $val->data[ Url::COMPONENT_FRAGMENT ] ) {
|
||||
throw new DomainException('oauth_redirect_uris_no_fragment');
|
||||
}
|
||||
}, NULL, NULL, 'oauth_wordpress_endpoint' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'oauth_pkce', $this->pkce ?: 'none', FALSE, array( 'options' => array( 'S256' => 'oauth_pkce_256', 'plain' => 'oauth_pkce_plain', 'none' => 'oauth_pkce_none' ) ), NULL, NULL, NULL, 'oauth_pkce' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Stack( 'oauth_redirect_uris', $redirectUris, NULL, array( 'stackFieldType' => 'Url', 'placeholder' => 'https://www.example.com/redirect_uri', 'allowedProtocols' => NULL ), function( $val ) {
|
||||
if ( !\in_array( \IPS\Request::i()->oauth_client_type, array('invision', 'wordpress') ) ) {
|
||||
$chosenGrantTypes = \IPS\Request::i()->oauth_client_type === 'public' ? \IPS\Request::i()->oauth_grant_types_public : \IPS\Request::i()->oauth_grant_types_confidential;
|
||||
$form->add( new Radio( 'oauth_pkce', $this->pkce ?: 'none', FALSE, array( 'options' => array( 'S256' => 'oauth_pkce_256', 'plain' => 'oauth_pkce_plain', 'none' => 'oauth_pkce_none' ) ), NULL, NULL, NULL, 'oauth_pkce' ) );
|
||||
$form->add( new Stack( 'oauth_redirect_uris', $redirectUris, NULL, array( 'stackFieldType' => 'Url', 'placeholder' => 'https://www.example.com/redirect_uri', 'allowedProtocols' => NULL ), function( $val ) {
|
||||
if ( !in_array( Request::i()->oauth_client_type, array('invision', 'wordpress') ) ) {
|
||||
$chosenGrantTypes = Request::i()->oauth_client_type === 'public' ? Request::i()->oauth_grant_types_public : Request::i()->oauth_grant_types_confidential;
|
||||
if ( !$val and ( isset( $chosenGrantTypes['authorization_code'] ) or isset( $chosenGrantTypes['implicit'] ) ) ) {
|
||||
throw new \DomainException('form_required');
|
||||
throw new DomainException('form_required');
|
||||
}
|
||||
if ( $val and $val instanceof \IPS\Http\Url and $val->data[ \IPS\Http\Url::COMPONENT_FRAGMENT ] ) {
|
||||
throw new \DomainException('oauth_redirect_uris_no_fragment');
|
||||
if ( $val and $val instanceof Url and $val->data[ Url::COMPONENT_FRAGMENT ] ) {
|
||||
throw new DomainException('oauth_redirect_uris_no_fragment');
|
||||
}
|
||||
}
|
||||
}, NULL, NULL, 'oauth_redirect_uris' ) );
|
||||
|
||||
$form->addHeader('oauth_authorization_screen');
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'oauth_prompt', $this->prompt, FALSE, array( 'options' => array( 'none' => 'oauth_prompt_none', 'automatic' => 'oauth_prompt_automatic', 'reauthorize' => 'oauth_prompt_reauthorize', 'login' => 'oauth_prompt_login' ) ) ) );
|
||||
$form->add( new Radio( 'oauth_prompt', $this->prompt, FALSE, array( 'options' => array( 'none' => 'oauth_prompt_none', 'automatic' => 'oauth_prompt_automatic', 'reauthorize' => 'oauth_prompt_reauthorize', 'login' => 'oauth_prompt_login' ) ) ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'oauth_choose_scopes', $this->choose_scopes, FALSE, array(), NULL, NULL, NULL, 'oauth_choose_scopes' ) );
|
||||
$form->add( new YesNo( 'oauth_choose_scopes', $this->choose_scopes, FALSE, array(), NULL, NULL, NULL, 'oauth_choose_scopes' ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'oauth_ucp', $this->ucp, FALSE ) );
|
||||
$form->add( new YesNo( 'oauth_ucp', $this->ucp, FALSE ) );
|
||||
|
||||
$form->addHeader('oauth_access_tokens');
|
||||
$form->add( new \IPS\Helpers\Form\Interval( 'oauth_access_token_length', $this->access_token_length, NULL, array( 'valueAs' => \IPS\Helpers\Form\Interval::HOURS, 'unlimited' => 0, 'unlimitedLang' => 'forever' ), NULL, NULL, NULL, 'oauth_access_token_length' ) );
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'oauth_use_refresh_tokens', $this->use_refresh_tokens, NULL, array( 'togglesOn' => array( 'oauth_refresh_token_length' ) ), NULL, NULL, NULL, 'oauth_use_refresh_tokens' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Interval( 'oauth_refresh_token_length', $this->refresh_token_length, NULL, array( 'valueAs' => \IPS\Helpers\Form\Interval::DAYS, 'unlimited' => 0, 'unlimitedLang' => 'forever' ), NULL, NULL, NULL, 'oauth_refresh_token_length' ) );
|
||||
$form->add( new Interval( 'oauth_access_token_length', $this->access_token_length, NULL, array( 'valueAs' => Interval::HOURS, 'unlimited' => 0, 'unlimitedLang' => 'forever' ), NULL, NULL, NULL, 'oauth_access_token_length' ) );
|
||||
$form->add( new YesNo( 'oauth_use_refresh_tokens', $this->use_refresh_tokens, NULL, array( 'togglesOn' => array( 'oauth_refresh_token_length' ) ), NULL, NULL, NULL, 'oauth_use_refresh_tokens' ) );
|
||||
$form->add( new Interval( 'oauth_refresh_token_length', $this->refresh_token_length, NULL, array( 'valueAs' => Interval::DAYS, 'unlimited' => 0, 'unlimitedLang' => 'forever' ), NULL, NULL, NULL, 'oauth_refresh_token_length' ) );
|
||||
|
||||
$form->addTab('oauth_scopes');
|
||||
$form->addMessage('oauth_scopes_blurb');
|
||||
$matrix = new \IPS\Helpers\Form\Matrix;
|
||||
$matrix = new Matrix;
|
||||
$matrix->classes[] = 'cApiPermissionsMatrix';
|
||||
$matrix->langPrefix = 'oauth_scope_';
|
||||
$matrix->columns = array(
|
||||
'name' => function( $key, $value, $data )
|
||||
{
|
||||
return new \IPS\Helpers\Form\Custom( $key, $value, FALSE, array(
|
||||
return new Custom( $key, $value, FALSE, array(
|
||||
'getHtml' => function( $field )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'api' )->oauthScopeField( $field->name, $field->value );
|
||||
return Theme::i()->getTemplate( 'api' )->oauthScopeField( $field->name, $field->value );
|
||||
}
|
||||
) );
|
||||
},
|
||||
'endpoints' => function( $key, $value, $data )
|
||||
{
|
||||
return new \IPS\Helpers\Form\Custom( $key, $value ?: array(), FALSE, array(
|
||||
return new Custom( $key, $value ?: array(), FALSE, array(
|
||||
'getHtml' => function( $field )
|
||||
{
|
||||
$endpoints = \IPS\Api\Controller::getAllEndpoints();
|
||||
$endpoints = Controller::getAllEndpoints();
|
||||
$endpointTree = [];
|
||||
foreach ( $endpoints as $key => $endpoint )
|
||||
{
|
||||
$pieces = explode('/', $key);
|
||||
$endpointTree[ $pieces[0] ][ $pieces[1] ][ $key ] = $endpoint;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'api' )->permissionsFieldHtml( $endpointTree, $field->name, $field->value );
|
||||
return Theme::i()->getTemplate( 'api' )->permissionsFieldHtml( $endpointTree, $field->name, $field->value );
|
||||
}
|
||||
) );
|
||||
}
|
||||
@@ -268,13 +296,13 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
if ( !$this->client_id )
|
||||
{
|
||||
$matrix->rows[] = array(
|
||||
'name' => array( 'key' => 'profile', 'desc' => \IPS\Member::loggedIn()->language()->get('oauth_default_scope_profile') ),
|
||||
'name' => array( 'key' => 'profile', 'desc' => Member::loggedIn()->language()->get('oauth_default_scope_profile') ),
|
||||
'endpoints' => array(
|
||||
'core/me/GETindex' => array( 'access' => TRUE, 'log' => FALSE ),
|
||||
)
|
||||
);
|
||||
$matrix->rows[] = array(
|
||||
'name' => array( 'key' => 'email', 'desc' => \IPS\Member::loggedIn()->language()->get('oauth_default_scope_email') ),
|
||||
'name' => array( 'key' => 'email', 'desc' => Member::loggedIn()->language()->get('oauth_default_scope_email') ),
|
||||
'endpoints' => array(
|
||||
'core/me/GETitem' => array( 'access' => TRUE, 'log' => FALSE ),
|
||||
)
|
||||
@@ -296,7 +324,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
/**
|
||||
* @brief Temporary storage for the client secret
|
||||
*/
|
||||
public $_clientSecret;
|
||||
public ?string $_clientSecret = null;
|
||||
|
||||
/**
|
||||
* [Node] Format form values from add/edit form for save
|
||||
@@ -304,10 +332,10 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
/* Normalise the settings */
|
||||
$originalClientType = isset( $values['oauth_client_type'] ) ? $values['oauth_client_type'] : $this->type;
|
||||
$originalClientType = $values['oauth_client_type'] ?? $this->type;
|
||||
|
||||
if ( $values['oauth_client_type'] === 'invision' )
|
||||
{
|
||||
@@ -321,13 +349,13 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
$values['oauth_api_access'] = 'rest';
|
||||
$values['scopes'] = array(
|
||||
array(
|
||||
'name' => array( 'key' => 'profile', 'desc' => \IPS\Member::loggedIn()->language()->get('oauth_default_scope_profile') ),
|
||||
'name' => array( 'key' => 'profile', 'desc' => Member::loggedIn()->language()->get('oauth_default_scope_profile') ),
|
||||
'endpoints' => array(
|
||||
'core/me/GETindex' => array( 'access' => TRUE, 'log' => FALSE ),
|
||||
)
|
||||
),
|
||||
array(
|
||||
'name' => array( 'key' => 'email', 'desc' => \IPS\Member::loggedIn()->language()->get('oauth_default_scope_email') ),
|
||||
'name' => array( 'key' => 'email', 'desc' => Member::loggedIn()->language()->get('oauth_default_scope_email') ),
|
||||
'endpoints' => array(
|
||||
'core/me/GETitem' => array( 'access' => TRUE, 'log' => FALSE ),
|
||||
)
|
||||
@@ -348,7 +376,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
$values['oauth_api_access'] = 'rest';
|
||||
$values['scopes'] = array(
|
||||
array(
|
||||
'name' => array( 'key' => 'email', 'desc' => \IPS\Member::loggedIn()->language()->get('oauth_default_scope_email') ),
|
||||
'name' => array( 'key' => 'email', 'desc' => Member::loggedIn()->language()->get('oauth_default_scope_email') ),
|
||||
'endpoints' => array(
|
||||
'core/me/GETindex' => array( 'access' => TRUE, 'log' => FALSE ),
|
||||
'core/me/GETitem' => array( 'access' => TRUE, 'log' => FALSE ),
|
||||
@@ -376,9 +404,9 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
{
|
||||
do
|
||||
{
|
||||
$values['oauth_client_id'] = \IPS\Login::generateRandomString( 32 );
|
||||
$values['oauth_client_id'] = Login::generateRandomString( 32 );
|
||||
}
|
||||
while ( \IPS\Db::i()->select( 'COUNT(*)', 'core_oauth_clients', array( 'oauth_client_id=?', $values['oauth_client_id'] ) )->first() );
|
||||
while ( Db::i()->select( 'COUNT(*)', 'core_oauth_clients', array( 'oauth_client_id=?', $values['oauth_client_id'] ) )->first() );
|
||||
}
|
||||
|
||||
/* And secret */
|
||||
@@ -386,7 +414,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
{
|
||||
if ( !$this->client_secret )
|
||||
{
|
||||
$this->_clientSecret = \IPS\Login::generateRandomString( 48 );
|
||||
$this->_clientSecret = Login::generateRandomString( 48 );
|
||||
$values['oauth_client_secret'] = password_hash( $this->_clientSecret, PASSWORD_DEFAULT );
|
||||
}
|
||||
$values['oauth_grant_types'] = $values['oauth_grant_types_confidential'];
|
||||
@@ -401,11 +429,11 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
|
||||
/* Save the name */
|
||||
$clientId = $this->client_id ?: $values['oauth_client_id'];
|
||||
\IPS\Lang::saveCustom( 'core', "core_oauth_client_{$clientId}", $values['oauth_client_name'] );
|
||||
Lang::saveCustom( 'core', "core_oauth_client_{$clientId}", $values['oauth_client_name'] );
|
||||
unset( $values['oauth_client_name'] );
|
||||
|
||||
/* Redirect URIs */
|
||||
if ( \in_array( $originalClientType, array( 'public', 'invision', 'wordpress' ) ) or \in_array( 'authorization_code', $values['oauth_grant_types'] ) or \in_array( 'implicit', $values['oauth_grant_types'] ) )
|
||||
if ( in_array( $originalClientType, array( 'public', 'invision', 'wordpress' ) ) or in_array( 'authorization_code', $values['oauth_grant_types'] ) or in_array( 'implicit', $values['oauth_grant_types'] ) )
|
||||
{
|
||||
$values['oauth_redirect_uris'] = json_encode( array_map( function( $url ) { return (string) $url; }, $values['oauth_redirect_uris'] ) );
|
||||
}
|
||||
@@ -422,7 +450,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
if ( $row['name']['key'] )
|
||||
{
|
||||
$scopes[ $row['name']['key'] ] = array(
|
||||
'description' => isset( $row['name']['desc'] ) ? $row['name']['desc'] : NULL,
|
||||
'description' => $row['name']['desc'] ?? NULL,
|
||||
'endpoints' => $row['endpoints']
|
||||
);
|
||||
}
|
||||
@@ -439,10 +467,10 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
*
|
||||
* @return NULL|array Null for no badge, or an array of badge data (0 => CSS class type, 1 => language string, 2 => optional raw HTML to show instead of language string)
|
||||
*/
|
||||
public function get__badge()
|
||||
public function get__badge(): ?array
|
||||
{
|
||||
return array(
|
||||
0 => 'ipsBadge ipsBadge_neutral ipsPos_right ipsMargin_right:half',
|
||||
0 => 'ipsBadge ipsBadge--neutral i-float_end i-margin-end_icon',
|
||||
1 => 'api_access_' . $this->api_access,
|
||||
);
|
||||
}
|
||||
@@ -452,7 +480,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get__description()
|
||||
protected function get__description(): ?string
|
||||
{
|
||||
return $this->client_id;
|
||||
}
|
||||
@@ -462,7 +490,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canCopy()
|
||||
public function canCopy(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -472,21 +500,21 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
* Example code explains return value
|
||||
*
|
||||
* @code
|
||||
array(
|
||||
array(
|
||||
'icon' => 'plus-circle', // Name of FontAwesome icon to use
|
||||
'title' => 'foo', // Language key to use for button's title parameter
|
||||
'link' => \IPS\Http\Url::internal( 'app=foo...' ) // URI to link to
|
||||
'class' => 'modalLink' // CSS Class to use on link (Optional)
|
||||
),
|
||||
... // Additional buttons
|
||||
);
|
||||
* array(
|
||||
* array(
|
||||
* 'icon' => 'plus-circle', // Name of FontAwesome icon to use
|
||||
* 'title' => 'foo', // Language key to use for button's title parameter
|
||||
* 'link' => \IPS\Http\Url::internal( 'app=foo...' ) // URI to link to
|
||||
* 'class' => 'modalLink' // CSS Class to use on link (Optional)
|
||||
* ),
|
||||
* ... // Additional buttons
|
||||
* );
|
||||
* @endcode
|
||||
* @param string $url Base URL
|
||||
* @param Url $url Base URL
|
||||
* @param bool $subnode Is this a subnode?
|
||||
* @return array
|
||||
*/
|
||||
public function getButtons( $url, $subnode=FALSE )
|
||||
public function getButtons( Url $url, bool $subnode=FALSE ):array
|
||||
{
|
||||
$buttons = array();
|
||||
|
||||
@@ -496,7 +524,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
'link' => $url->setQueryString( array( 'do' => 'view', 'client_id' => $this->client_id ) )
|
||||
);
|
||||
|
||||
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'oauth_tokens' ) )
|
||||
if ( Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'oauth_tokens' ) )
|
||||
{
|
||||
$buttons['tokens'] = array(
|
||||
'icon' => 'key',
|
||||
@@ -508,37 +536,37 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
$_parentButtons = parent::getButtons( $url, $subnode );
|
||||
if ( isset( $_parentButtons['delete'] ) )
|
||||
{
|
||||
$_parentButtons['delete']['data'] = array( 'confirm' => '', 'confirmSubMessage' => \IPS\Member::loggedIn()->language()->addToStack('oauth_client_delete_warning') );
|
||||
$_parentButtons['delete']['data'] = array( 'confirm' => '', 'confirmSubMessage' => Member::loggedIn()->language()->addToStack('oauth_client_delete_warning') );
|
||||
}
|
||||
|
||||
return $buttons + $_parentButtons;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate or renew an access token
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member or NULL for client_credentials
|
||||
* @param array|null $scopes Array of scopes or NULL if none were requested
|
||||
* @param string $grantType Type of grant
|
||||
* @param bool $skipRefreshToken If TRUE, will not generate a refresh token (for example, when using implicit grant type)
|
||||
* @param string|NULL $authorizationCode The authorization code which generated the token, if applicable
|
||||
* @param string|NULL $userAgent The user agent that the user performed authentication on, if known
|
||||
* @param string|NULL $issueUserAgent The user agent that the access token was issued to, if known
|
||||
* @param \IPS\Member\Device|NULL $device The device used to obtain this access token, if known
|
||||
* @param array $tokenToRefresh If we are refreshing, the existing access token to refresh
|
||||
* @return array
|
||||
* @param Member|NULL $member The member or NULL for client_credentials
|
||||
* @param array|null $scopes Array of scopes or NULL if none were requested
|
||||
* @param string $grantType Type of grant
|
||||
* @param bool $skipRefreshToken If TRUE, will not generate a refresh token (for example, when using implicit grant type)
|
||||
* @param string|NULL $authorizationCode The authorization code which generated the token, if applicable
|
||||
* @param string|null $authUserAgent
|
||||
* @param string|null $grantUserAgent
|
||||
* @param Device|NULL $device The device used to obtain this access token, if known
|
||||
* @param array|null $tokenToRefresh If we are refreshing, the existing access token to refresh
|
||||
* @return array
|
||||
*/
|
||||
public function generateAccessToken( ?\IPS\Member $member, $scopes, $grantType, $skipRefreshToken = FALSE, $authorizationCode = NULL, $authUserAgent = NULL, $grantUserAgent = NULL, \IPS\Member\Device $device = NULL, $tokenToRefresh = NULL )
|
||||
public function generateAccessToken( ?Member $member, ?array $scopes, string $grantType, bool $skipRefreshToken = FALSE, ?string $authorizationCode = NULL, ?string $authUserAgent = NULL, ?string $grantUserAgent = NULL, ?Device $device = NULL, ?array $tokenToRefresh = NULL ) : array
|
||||
{
|
||||
do
|
||||
{
|
||||
$accessToken = \IPS\Login::generateRandomString( 64 );
|
||||
$accessToken = Login::generateRandomString( 64 );
|
||||
}
|
||||
while ( $this->validateAccessToken( $accessToken ) );
|
||||
|
||||
$data = array(
|
||||
'client_id' => $this->client_id,
|
||||
'member_id' => $member ? $member->member_id : NULL,
|
||||
'member_id' => $member?->member_id,
|
||||
'access_token' => $accessToken,
|
||||
'access_token_expires' => $this->access_token_length ? ( time() + ( $this->access_token_length * 3600 ) ) : NULL,
|
||||
'refresh_token' => NULL,
|
||||
@@ -548,7 +576,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
'issued' => time(),
|
||||
'auth_user_agent' => $authUserAgent,
|
||||
'issue_user_agent' => $grantUserAgent,
|
||||
'device_key' => $device ? $device->device_key : NULL,
|
||||
'device_key' => $device?->device_key,
|
||||
'status' => 'active',
|
||||
);
|
||||
|
||||
@@ -558,7 +586,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
{
|
||||
do
|
||||
{
|
||||
$data['refresh_token'] = \IPS\Login::generateRandomString( 64 );
|
||||
$data['refresh_token'] = Login::generateRandomString( 64 );
|
||||
}
|
||||
while ( $this->validateRefreshToken( $data['refresh_token'] ) );
|
||||
|
||||
@@ -575,12 +603,12 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
|
||||
if ( $grantType === 'refresh_token' and $tokenToRefresh )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'client_id=? AND access_token=?', $tokenToRefresh['client_id'], $tokenToRefresh['access_token'] ) );
|
||||
\IPS\Db::i()->insert( 'core_oauth_server_access_tokens', $data );
|
||||
Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'client_id=? AND access_token=?', $tokenToRefresh['client_id'], $tokenToRefresh['access_token'] ) );
|
||||
Db::i()->insert( 'core_oauth_server_access_tokens', $data );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_oauth_server_access_tokens', $data );
|
||||
Db::i()->insert( 'core_oauth_server_access_tokens', $data );
|
||||
if ( $member )
|
||||
{
|
||||
$member->logHistory( 'core', 'oauth', array( 'type' => 'issued_access_token', 'client' => $this->client_id, 'grant' => $grantType, 'scopes' => $scopes ), FALSE );
|
||||
@@ -591,7 +619,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
{
|
||||
$device->last_seen = time();
|
||||
$device->save();
|
||||
$device->logIpAddress( \IPS\Request::i()->ipAddress() );
|
||||
$device->logIpAddress( Request::i()->ipAddress() );
|
||||
}
|
||||
|
||||
$data['access_token'] = $this->client_id . '_' . $data['access_token'];
|
||||
@@ -603,27 +631,27 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
* Get access token details, checking it isn't expired
|
||||
*
|
||||
* @param string $accessToken The access token
|
||||
* @return \IPS\Member|NULL
|
||||
* @throws \UnderflowException
|
||||
* @throws \IPS\Api\Exception
|
||||
* @return array
|
||||
* @throws UnderflowException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function accessTokenDetails( $accessToken )
|
||||
public static function accessTokenDetails( string $accessToken ) : array
|
||||
{
|
||||
$exploded = explode( '_', $accessToken );
|
||||
|
||||
if ( !isset( $exploded[0] ) or !isset( $exploded[1] ) )
|
||||
{
|
||||
throw new \UnderflowException;
|
||||
throw new UnderflowException;
|
||||
}
|
||||
|
||||
$return = \IPS\Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'client_id=? AND access_token=?', $exploded[0], $exploded[1] ) )->first();
|
||||
$return = Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'client_id=? AND access_token=?', $exploded[0], $exploded[1] ) )->first();
|
||||
if ( $return['status'] == 'revoked' )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'REVOKED_ACCESS_TOKEN', '1S290/F', 401, 'invalid_token' );
|
||||
throw new Exception( 'REVOKED_ACCESS_TOKEN', '1S290/F', 401, 'invalid_token' );
|
||||
}
|
||||
if ( $return['access_token_expires'] and $return['access_token_expires'] < time() )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'EXPIRED_ACCESS_TOKEN', '1S290/E', 401, 'invalid_token' );
|
||||
throw new Exception( 'EXPIRED_ACCESS_TOKEN', '1S290/E', 401, 'invalid_token' );
|
||||
}
|
||||
|
||||
return $return;
|
||||
@@ -633,41 +661,43 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
* Validate an access token
|
||||
*
|
||||
* @param string $accessToken The access token
|
||||
* @return \IPS\Member|NULL
|
||||
* @return Member|NULL
|
||||
*/
|
||||
public function validateAccessToken( $accessToken )
|
||||
public function validateAccessToken( string $accessToken ) : ?Member
|
||||
{
|
||||
try
|
||||
{
|
||||
$row = \IPS\Db::i()->select( array( 'member_id', 'access_token_expires' ), 'core_oauth_server_access_tokens', array( 'client_id=? AND access_token=?', $this->client_id, $accessToken ) )->first();
|
||||
$row = Db::i()->select( array( 'member_id', 'access_token_expires' ), 'core_oauth_server_access_tokens', array( 'client_id=? AND access_token=?', $this->client_id, $accessToken ) )->first();
|
||||
if ( $row['status'] == 'revoked' )
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if ( $row['access_token_expires'] and $row['access_token_expires'] < time() )
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
$member = \IPS\Member::load( $row['member_id'] );
|
||||
$member = Member::load( $row['member_id'] );
|
||||
if ( $member->member_id )
|
||||
{
|
||||
return $member;
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an existing access token with particular scopes, if they exist
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member or NULL for client_credentials
|
||||
* @param Member|NULL $member The member or NULL for client_credentials
|
||||
* @param array $scopes The scopes
|
||||
* @return array|NULL
|
||||
*/
|
||||
public function getAccessToken( \IPS\Member $member = NULL, $scopes = array() )
|
||||
public function getAccessToken( ?Member $member = NULL, array $scopes = array() ) : ?array
|
||||
{
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'client_id=? AND member_id=?', $this->client_id, $member->member_id ) ) as $row )
|
||||
foreach ( Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'client_id=? AND member_id=?', $this->client_id, $member->member_id ) ) as $row )
|
||||
{
|
||||
if ( $row['status'] == 'revoked' )
|
||||
{
|
||||
@@ -688,13 +718,15 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( array_diff( $scopes, $row['scope'] ? json_decode( $row['scope'] ) : array() ) ) )
|
||||
if ( count( array_diff( $scopes, $row['scope'] ? json_decode( $row['scope'] ) : array() ) ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -703,23 +735,25 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
* @param string $refreshToken The refresh token
|
||||
* @return array|NULL
|
||||
*/
|
||||
public function validateRefreshToken( $refreshToken )
|
||||
public function validateRefreshToken( string $refreshToken ) : ?array
|
||||
{
|
||||
try
|
||||
{
|
||||
$row = \IPS\Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'client_id=? AND refresh_token=?', $this->client_id, $refreshToken ) )->first();
|
||||
$row = Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'client_id=? AND refresh_token=?', $this->client_id, $refreshToken ) )->first();
|
||||
if ( $row['status'] == 'revoked' )
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
if ( $row['refresh_token_expires'] and $row['refresh_token_expires'] < time() )
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -731,7 +765,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
* @param string $method Method
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function scopesCanAccess( $authorizedScopes, $app, $controller, $method )
|
||||
public function scopesCanAccess( array $authorizedScopes, string $app, string $controller, string $method ) : ?string
|
||||
{
|
||||
$scopes = $this->scopes ? json_decode( $this->scopes, TRUE ) : array();
|
||||
|
||||
@@ -739,7 +773,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
{
|
||||
if ( isset( $scopes[ $scope ] ) )
|
||||
{
|
||||
if ( isset( $scopes[ $scope ]['endpoints']["{$app}/{$controller}/{$method}"] ) and $scopes[ $scope ]['endpoints']["{$app}/{$controller}/{$method}"]['access'] == TRUE )
|
||||
if ( isset( $scopes[ $scope ]['endpoints']["{$app}/{$controller}/{$method}"] ) and $scopes[$scope]['endpoints']["{$app}/{$controller}/{$method}"]['access'] )
|
||||
{
|
||||
return $scope;
|
||||
}
|
||||
@@ -758,7 +792,7 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
* @param string $method Method
|
||||
* @return bool
|
||||
*/
|
||||
public function scopeShouldLog( $scope, $app, $controller, $method )
|
||||
public function scopeShouldLog( string $scope, string $app, string $controller, string $method ) : bool
|
||||
{
|
||||
$scopes = $this->scopes ? json_decode( $this->scopes, TRUE ) : array();
|
||||
return isset( $scopes[ $scope ]['endpoints']["{$app}/{$controller}/{$method}"] ) and isset( $scopes[ $scope ]['endpoints']["{$app}/{$controller}/{$method}"]['log'] ) and $scopes[ $scope ]['endpoints']["{$app}/{$controller}/{$method}"]['log'] == TRUE;
|
||||
@@ -767,13 +801,13 @@ class _OAuthClient extends \IPS\Node\Model
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
public function delete(): void
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'client_id=?', $this->client_id ) );
|
||||
\IPS\Db::i()->delete( 'core_oauth_server_authorization_codes', array( 'client_id=?', $this->client_id ) );
|
||||
Db::i()->delete( 'core_oauth_server_access_tokens', array( 'client_id=?', $this->client_id ) );
|
||||
Db::i()->delete( 'core_oauth_server_authorization_codes', array( 'client_id=?', $this->client_id ) );
|
||||
|
||||
return parent::delete();
|
||||
parent::delete();
|
||||
}
|
||||
}
|
||||
@@ -11,79 +11,86 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Db;
|
||||
use IPS\Db\Select;
|
||||
use IPS\Member;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use Iterator;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Response
|
||||
*/
|
||||
class _PaginatedResponse extends Response
|
||||
class PaginatedResponse extends Response
|
||||
{
|
||||
/**
|
||||
* @brief HTTP Response Code
|
||||
*/
|
||||
public $httpCode;
|
||||
public int $httpCode = 200;
|
||||
|
||||
/**
|
||||
* @brief Iterator (usually a select query)
|
||||
*/
|
||||
protected $iterator;
|
||||
protected ?Iterator $iterator = null;
|
||||
|
||||
/**
|
||||
* @brief Current page
|
||||
*/
|
||||
protected $page = 1;
|
||||
protected int $page = 1;
|
||||
|
||||
/**
|
||||
* @brief Results per page
|
||||
*/
|
||||
protected $resultsPerPage = 25;
|
||||
protected int $resultsPerPage = 25;
|
||||
|
||||
/**
|
||||
* @brief ActiveRecord class
|
||||
*/
|
||||
protected $activeRecordClass;
|
||||
protected string $activeRecordClass = '';
|
||||
|
||||
/**
|
||||
* @brief Total Count
|
||||
*/
|
||||
protected $count;
|
||||
protected int $count = 0;
|
||||
|
||||
/**
|
||||
* @brief The member making the API request or NULL for API Key / client_credentials
|
||||
*/
|
||||
protected $authorizedMember;
|
||||
protected ?Member $authorizedMember = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $httpCode HTTP Response code
|
||||
* @param \Iterator $iterator Select query or \IPS\Patterns\ActiveRecordIterator instance
|
||||
* @param Iterator|array $iterator Select query or \IPS\Patterns\ActiveRecordIterator instance
|
||||
* @param int $page Current page
|
||||
* @param string $activeRecordClass ActiveRecord class
|
||||
* @param int|NULL $count Total Count
|
||||
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @param int $perPage Number of results per page
|
||||
* @param Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @param int|null $perPage Number of results per page
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $httpCode, $iterator, $page, $activeRecordClass, $count, \IPS\Member $authorizedMember = NULL, $perPage=NULL )
|
||||
public function __construct( int $httpCode, Iterator $iterator, int $page, string $activeRecordClass, ?int $count, ?Member $authorizedMember = NULL, ?int $perPage=NULL )
|
||||
{
|
||||
$this->httpCode = $httpCode;
|
||||
$page = (int) $page;
|
||||
$this->page = $page > 0 ? $page : 1;
|
||||
$this->iterator = $iterator;
|
||||
$this->activeRecordClass = $activeRecordClass;
|
||||
$this->count = (int) $count;
|
||||
$this->resultsPerPage = $perPage ? (int) $perPage : 25;
|
||||
$this->resultsPerPage = $perPage ?: 25;
|
||||
$this->authorizedMember = $authorizedMember;
|
||||
|
||||
if( $this->iterator instanceof \IPS\Db\Select )
|
||||
if( $this->iterator instanceof Select )
|
||||
{
|
||||
/* Limit the query before calling count(), as this runs the query so it cannot be modified after */
|
||||
$this->iterator->query .= \IPS\Db::i()->compileLimitClause( array( ( $this->page - 1 ) * $this->resultsPerPage, $this->resultsPerPage ) );
|
||||
$this->iterator->query .= Db::i()->compileLimitClause( array( ( $this->page - 1 ) * $this->resultsPerPage, $this->resultsPerPage ) );
|
||||
|
||||
if ( $count === NULL )
|
||||
{
|
||||
@@ -97,13 +104,13 @@ class _PaginatedResponse extends Response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOutput()
|
||||
public function getOutput() : array
|
||||
{
|
||||
$results = array();
|
||||
|
||||
if ( $this->activeRecordClass )
|
||||
{
|
||||
if( $this->iterator instanceof \IPS\Patterns\ActiveRecordIterator )
|
||||
if( $this->iterator instanceof ActiveRecordIterator )
|
||||
{
|
||||
foreach ( $this->iterator as $result )
|
||||
{
|
||||
@@ -112,7 +119,7 @@ class _PaginatedResponse extends Response
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ( new \IPS\Patterns\ActiveRecordIterator( $this->iterator, $this->activeRecordClass ) as $result )
|
||||
foreach ( new ActiveRecordIterator( $this->iterator, $this->activeRecordClass ) as $result )
|
||||
{
|
||||
$results[] = $result->apiOutput( $this->authorizedMember );
|
||||
}
|
||||
|
||||
+12
-9
@@ -11,35 +11,38 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Response
|
||||
*/
|
||||
class _Response
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* @brief HTTP Response Code
|
||||
*/
|
||||
public $httpCode;
|
||||
public int $httpCode;
|
||||
|
||||
/**
|
||||
* @brief Data
|
||||
*/
|
||||
protected $data;
|
||||
protected mixed $data = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $httpCode HTTP Response code
|
||||
* @param array $data Data to return
|
||||
* @param mixed $data Data to return
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $httpCode, $data )
|
||||
public function __construct( int $httpCode, mixed $data )
|
||||
{
|
||||
$this->httpCode = $httpCode;
|
||||
$this->data = $data;
|
||||
@@ -48,9 +51,9 @@ class _Response
|
||||
/**
|
||||
* Data to output
|
||||
*
|
||||
* @return string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOutput()
|
||||
public function getOutput() : mixed
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
+80
-60
@@ -11,42 +11,62 @@
|
||||
namespace IPS\Api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\core\Form\WebhookSelector;
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\Url as FormUrl;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Log;
|
||||
use IPS\Member;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Theme;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook
|
||||
*/
|
||||
class _Webhook extends \IPS\Node\Model
|
||||
class Webhook extends Model
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
protected static array $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_api_webhooks';
|
||||
public static ?string $databaseTable = 'core_api_webhooks';
|
||||
|
||||
/**
|
||||
* @brief cache for get_url()
|
||||
*/
|
||||
protected $_url;
|
||||
protected mixed $_url = NULL;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Caches
|
||||
* @note Defined cache keys will be cleared automatically as needed
|
||||
*/
|
||||
protected $caches = array('webhooks');
|
||||
protected array $caches = array('webhooks');
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'api_webhooks';
|
||||
public static string $nodeTitle = 'api_webhooks';
|
||||
|
||||
/**
|
||||
* @brief [Node] ACP Restrictions
|
||||
@@ -64,7 +84,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
|
||||
* @endcode
|
||||
*/
|
||||
protected static $restrictions = array(
|
||||
protected static ?array $restrictions = array(
|
||||
'app' => 'core',
|
||||
'module' => 'applications',
|
||||
'prefix' => 'api_',
|
||||
@@ -75,7 +95,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultValues()
|
||||
public function setDefaultValues() : void
|
||||
{
|
||||
$this->content_type = 'application/x-www-form-urlencoded';
|
||||
$this->_data['filters'] = '{}';
|
||||
@@ -86,14 +106,14 @@ class _Webhook extends \IPS\Node\Model
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
*
|
||||
* @param \IPS\Helpers\Form $form The form
|
||||
* @param Form $form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( &$form )
|
||||
public function form( Form &$form ) : void
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Url( 'url', $this->url ?? "", TRUE ) );
|
||||
$form->add( new FormUrl( 'url', $this->url ?? "", TRUE ) );
|
||||
|
||||
if( \in_array($this->content_type, ['application/x-www-form-urlencoded', 'application/json']))
|
||||
if( in_array($this->content_type, ['application/x-www-form-urlencoded', 'application/json']))
|
||||
{
|
||||
$selectedHeader = $this->content_type;
|
||||
$customHeader = '';
|
||||
@@ -103,13 +123,13 @@ class _Webhook extends \IPS\Node\Model
|
||||
$selectedHeader = 'custom';
|
||||
$customHeader = $this->content_type;
|
||||
}
|
||||
$form->add( new \IPS\Helpers\Form\Select('webhook_content_type', $selectedHeader, TRUE , ['options' => ['application/x-www-form-urlencoded' => 'x-www-form-urlencoded', 'application/json' => 'application/json', 'custom' => 'Other'] , 'toggles'=> ['custom' => ['webhook_content_type_other'] ] ] ) );
|
||||
$form->add( new \IPS\Helpers\Form\Text('webhook_content_type_other', $customHeader, FALSE , [], NULL, NULL, NULL, 'webhook_content_type_other' ) );
|
||||
$form->add( new Select('webhook_content_type', $selectedHeader, TRUE , ['options' => ['application/x-www-form-urlencoded' => 'x-www-form-urlencoded', 'application/json' => 'application/json', 'custom' => 'Other'] , 'toggles'=> ['custom' => ['webhook_content_type_other'] ] ] ) );
|
||||
$form->add( new Text('webhook_content_type_other', $customHeader, FALSE , [], NULL, NULL, NULL, 'webhook_content_type_other' ) );
|
||||
$events = [];
|
||||
|
||||
foreach ( \IPS\Api\Webhook::getAvailableWebhooks() as $app => $hooks )
|
||||
foreach (Webhook::getAvailableWebhooks() as $app => $hooks )
|
||||
{
|
||||
if( \count( $hooks ))
|
||||
if( count( $hooks ))
|
||||
{
|
||||
foreach( $hooks as $hook => $params)
|
||||
{
|
||||
@@ -118,7 +138,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
}
|
||||
}
|
||||
|
||||
$form->add( new \IPS\core\Form\WebhookSelector('webhook_events', $this->events ?? [], TRUE , [ 'options' => $events] ) );
|
||||
$form->add( new WebhookSelector('webhook_events', $this->events ?? [], TRUE , [ 'options' => $events] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +147,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
$values['events'] = $values['webhook_events'];
|
||||
|
||||
@@ -145,7 +165,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function get__title()
|
||||
public function get__title(): string
|
||||
{
|
||||
return (string) $this->url;
|
||||
}
|
||||
@@ -155,9 +175,9 @@ class _Webhook extends \IPS\Node\Model
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get__description()
|
||||
protected function get__description(): ?string
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'api', 'core' )->webhookDesc( $this );
|
||||
return Theme::i()->getTemplate( 'api', 'core' )->webhookDesc( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,16 +188,16 @@ class _Webhook extends \IPS\Node\Model
|
||||
* @param array $filters Filters
|
||||
* @return void
|
||||
*/
|
||||
public static function fire( $event, $data = NULL, $filters = array() )
|
||||
public static function fire( string $event, mixed $data = NULL, array $filters = array() ) : void
|
||||
{
|
||||
/* This will intentionally not call $data::apiOutput to avoid unnecessary overhead here */
|
||||
\IPS\Log::debug( print_r( array_merge(['event' => $event], ['payload' => $data], ['filters' => $filters ]) , TRUE ), 'webhook_fire_call');
|
||||
Log::debug( print_r( array_merge(['event' => $event], ['payload' => $data], ['filters' => $filters ]) , TRUE ), 'webhook_fire_call');
|
||||
|
||||
/* Get our webhooks from cache */
|
||||
if ( !isset( \IPS\Data\Store::i()->webhooks ) )
|
||||
if ( !isset( Store::i()->webhooks ) )
|
||||
{
|
||||
$webhooks = array();
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_api_webhooks', array('enabled=1') ) as $row )
|
||||
foreach ( Db::i()->select( '*', 'core_api_webhooks', array('enabled=1') ) as $row )
|
||||
{
|
||||
foreach ( explode( ',', $row['events'] ) as $e )
|
||||
{
|
||||
@@ -194,24 +214,24 @@ class _Webhook extends \IPS\Node\Model
|
||||
);
|
||||
}
|
||||
}
|
||||
\IPS\Data\Store::i()->webhooks = $webhooks;
|
||||
Store::i()->webhooks = $webhooks;
|
||||
}
|
||||
|
||||
/* If we have webhooks for this event... */
|
||||
$enable = FALSE;
|
||||
if ( isset( \IPS\Data\Store::i()->webhooks[ $event ] ) )
|
||||
if ( isset( Store::i()->webhooks[ $event ] ) )
|
||||
{
|
||||
/* Normalise data */
|
||||
if ( \is_object( $data ) and method_exists( $data, 'apiOutput' ) )
|
||||
if ( is_object( $data ) and method_exists( $data, 'apiOutput' ) )
|
||||
{
|
||||
$data = $data->apiOutput();
|
||||
}
|
||||
else if ( \is_array( $data ) )
|
||||
else if ( is_array( $data ) )
|
||||
{
|
||||
foreach ( $data as $key => &$item )
|
||||
{
|
||||
/* Normalise data */
|
||||
if ( \is_object( $item ) and method_exists( $item, 'apiOutput' ) )
|
||||
if ( is_object( $item ) and method_exists( $item, 'apiOutput' ) )
|
||||
{
|
||||
$item = $item->apiOutput();
|
||||
}
|
||||
@@ -219,12 +239,12 @@ class _Webhook extends \IPS\Node\Model
|
||||
}
|
||||
|
||||
/* We need to replace and langstring hashes ( like node names) */
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $data );
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $data );
|
||||
|
||||
$data = $data ? json_encode( $data ) : NULL;
|
||||
|
||||
/* Loop through each one... */
|
||||
foreach ( \IPS\Data\Store::i()->webhooks[ $event ] as $id => $webhook )
|
||||
foreach ( Store::i()->webhooks[ $event ] as $id => $webhook )
|
||||
{
|
||||
/* Skip over it if the filters don't match */
|
||||
if ( isset( $webhook['filters'][ $event ] ) )
|
||||
@@ -233,9 +253,9 @@ class _Webhook extends \IPS\Node\Model
|
||||
{
|
||||
if ( isset( $filters[ $k ] ) )
|
||||
{
|
||||
if ( \is_array( $v ) and !\is_array( $filters[ $k ] ) )
|
||||
if ( is_array( $v ) and !is_array( $filters[ $k ] ) )
|
||||
{
|
||||
if ( !\in_array( $filters[ $k ], $v ) )
|
||||
if ( !in_array( $filters[ $k ], $v ) )
|
||||
{
|
||||
continue 2;
|
||||
}
|
||||
@@ -253,7 +273,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
|
||||
/* Insert it */
|
||||
$enable = TRUE;
|
||||
\IPS\Db::i()->insert( 'core_api_webhook_fires', [
|
||||
Db::i()->insert( 'core_api_webhook_fires', [
|
||||
'webhook' => $id,
|
||||
'event' => $event,
|
||||
'data' => $data,
|
||||
@@ -266,27 +286,27 @@ class _Webhook extends \IPS\Node\Model
|
||||
/* Make sure the task is enabled */
|
||||
if ( $enable )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 'webhooks' ) );
|
||||
Db::i()->update( 'core_tasks', array( 'enabled' => 1 ), array( '`key`=?', 'webhooks' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API Key
|
||||
*
|
||||
* @return \IPS\Api\Key
|
||||
* @return Key
|
||||
*/
|
||||
public function get_api_key()
|
||||
public function get_api_key() : Key
|
||||
{
|
||||
return \IPS\Api\Key::load( $this->_data['api_key'] );
|
||||
return Key::load( $this->_data['api_key'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set API Key
|
||||
*
|
||||
* @param \IPS\Api\Key $key The API Key
|
||||
* @param Key $key The API Key
|
||||
* @return void
|
||||
*/
|
||||
public function set_api_key( \IPS\Api\Key $key )
|
||||
public function set_api_key(Key $key ) : void
|
||||
{
|
||||
$this->_data['api_key'] = $key->id;
|
||||
}
|
||||
@@ -296,7 +316,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_events()
|
||||
public function get_events() : array
|
||||
{
|
||||
return explode( ',', $this->_data['events'] );
|
||||
}
|
||||
@@ -307,9 +327,9 @@ class _Webhook extends \IPS\Node\Model
|
||||
* @param string|array $events List of events
|
||||
* @return void
|
||||
*/
|
||||
public function set_events( $events )
|
||||
public function set_events( string|array $events ) : void
|
||||
{
|
||||
if( !\is_array( $events ) )
|
||||
if( !is_array( $events ) )
|
||||
{
|
||||
$events = [$events];
|
||||
}
|
||||
@@ -319,20 +339,20 @@ class _Webhook extends \IPS\Node\Model
|
||||
/**
|
||||
* Get URL
|
||||
*
|
||||
* @return
|
||||
* @return Url|string
|
||||
*/
|
||||
public function get_url()
|
||||
public function get_url() : Url|string
|
||||
{
|
||||
return $this->_data['url'] ? new \IPS\Http\Url( $this->_data['url'] ) : '';
|
||||
return $this->_data['url'] ? new Url( $this->_data['url'] ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Url
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL
|
||||
* @param Url $url The URL
|
||||
* @return void
|
||||
*/
|
||||
public function set_url( \IPS\Http\Url $url )
|
||||
public function set_url( Url $url ) : void
|
||||
{
|
||||
$this->_url = $url;
|
||||
$this->_data['url'] = (string) $this->_url;
|
||||
@@ -343,7 +363,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_filters()
|
||||
public function get_filters() : array
|
||||
{
|
||||
return $this->_data['filters'] ? json_decode( $this->_data['filters'], TRUE ) : array();
|
||||
}
|
||||
@@ -354,7 +374,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
* @param array $filters Filter
|
||||
* @return void
|
||||
*/
|
||||
public function set_filters( array $filters )
|
||||
public function set_filters( array $filters ) : void
|
||||
{
|
||||
$this->_data['filters'] = json_encode( $filters );
|
||||
}
|
||||
@@ -362,13 +382,13 @@ class _Webhook extends \IPS\Node\Model
|
||||
/**
|
||||
* Get output for API
|
||||
*
|
||||
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @param Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
|
||||
* @return array
|
||||
* @apiresponse int id ID number
|
||||
* @apiresponse array events List of events this hook is subscribed to
|
||||
* @apiresponse string url URL to send webhook to
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL )
|
||||
public function apiOutput( Member $authorizedMember = NULL ): array
|
||||
{
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
@@ -377,10 +397,10 @@ class _Webhook extends \IPS\Node\Model
|
||||
);
|
||||
}
|
||||
|
||||
public static function getAvailableWebhooks()
|
||||
public static function getAvailableWebhooks() : array
|
||||
{
|
||||
$hooks = [];
|
||||
foreach ( \IPS\Application::applications() as $app )
|
||||
foreach ( Application::applications() as $app )
|
||||
{
|
||||
if( $app->enabled )
|
||||
{
|
||||
@@ -400,7 +420,7 @@ class _Webhook extends \IPS\Node\Model
|
||||
* @param mixed $where Where clause
|
||||
* @return array
|
||||
*/
|
||||
public static function search( $column, $query, $order=NULL, $where=array() )
|
||||
public static function search( string $column, string $query, string $order=NULL, mixed $where=array() ): array
|
||||
{
|
||||
if ( $column === '_title' )
|
||||
{
|
||||
@@ -420,9 +440,9 @@ class _Webhook extends \IPS\Node\Model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
public function delete(): void
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_api_webhook_fires', array( 'webhook=?', $this->id ) );
|
||||
Db::i()->delete( 'core_api_webhook_fires', array( 'webhook=?', $this->id ) );
|
||||
parent::delete();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user