Version 4.5.0
This commit is contained in:
1 parent
1a0c7fd3c2
commit
f79dcf067a
3791 files changed
+248032
-76372
No files matched your search
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Core mutations
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\Types;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core mutationss GraphQL API
|
||||
*/
|
||||
abstract class _Mutation
|
||||
{
|
||||
/**
|
||||
* Get the supported query types in this app
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function mutations()
|
||||
{
|
||||
return [
|
||||
'follow' => new \IPS\core\api\GraphQL\Mutations\Follow(),
|
||||
'unfollow' => new \IPS\core\api\GraphQL\Mutations\Unfollow(),
|
||||
'markNotificationRead' => new \IPS\core\api\GraphQL\Mutations\MarkNotificationRead(),
|
||||
'sessionStart' => new \IPS\core\api\GraphQL\Mutations\SessionStart(),
|
||||
'sessionEnd' => new \IPS\core\api\GraphQL\Mutations\SessionEnd(),
|
||||
'uploadAttachment' => new \IPS\core\api\GraphQL\Mutations\UploadAttachment(),
|
||||
'deleteAttachment' => new \IPS\core\api\GraphQL\Mutations\DeleteAttachment(),
|
||||
'ignoreMember' => new \IPS\core\api\GraphQL\Mutations\IgnoreUser(),
|
||||
'changeNotificationSetting' => new \IPS\core\api\GraphQL\Mutations\ChangeNotificationSetting(),
|
||||
'leaveConversation' => new \IPS\core\api\GraphQL\Mutations\Messenger\LeaveConversation(),
|
||||
'removeConversationUser' => new \IPS\core\api\GraphQL\Mutations\Messenger\RemoveConversationUser(),
|
||||
'addConversationUser' => new \IPS\core\api\GraphQL\Mutations\Messenger\AddConversationUser(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Change a notification setting
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 4 Aug 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change notification setting mutation for GraphQL API
|
||||
*/
|
||||
class _ChangeNotificationSetting
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Change a notification setting";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'extension' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'type' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'email' => TypeRegistry::boolean(),
|
||||
'push' => TypeRegistry::boolean(),
|
||||
'inline' => TypeRegistry::boolean(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\Api\GraphQL\TypeRegistry::notificationType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
$pieces = explode('_', $args['extension']);
|
||||
|
||||
if( \count( $pieces ) !== 2 )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_EXTENSION', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
$extensions = \IPS\Application::load( $pieces[0] )->extensions('core', 'Notifications');
|
||||
|
||||
if( !isset( $extensions[ $pieces[1] ] ) )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_EXTENSION', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
$extensionOptions = \IPS\Notification::availableOptions( \IPS\Member::loggedIn(), $extensions[ $pieces[1] ] );
|
||||
|
||||
if( !isset( $extensionOptions[ $args['type'] ] ) || $extensionOptions[ $args['type'] ]['type'] !== 'standard' ) // Only standard types supported right now
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_TYPE', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
$value = array();
|
||||
$extensionType = $extensionOptions[ $args['type'] ];
|
||||
$options = $extensionType['options'];
|
||||
|
||||
foreach( $options as $type => $typeSettings )
|
||||
{
|
||||
// If the mutation is trying to change a setting...
|
||||
if( isset( $args[ $type ] ) )
|
||||
{
|
||||
if( $args[ $type ] === TRUE && $typeSettings['editable'] == TRUE && !\in_array( $type, $extensionType['disabled'] ) )
|
||||
{
|
||||
$value[ $type ] = $type;
|
||||
}
|
||||
}
|
||||
// If we're taking the existing value
|
||||
else if( $typeSettings['value'] === TRUE )
|
||||
{
|
||||
$value[ $type ] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
// If push is true, inline must also be true
|
||||
if( isset( $value['push'] ) )
|
||||
{
|
||||
$value['inline'] = 'inline';
|
||||
}
|
||||
|
||||
foreach ( $extensionType['notificationTypes'] as $notificationKey )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_notification_preferences', array(
|
||||
'member_id' => \IPS\Member::loggedIn()->member_id,
|
||||
'notification_key' => $notificationKey,
|
||||
'preference' => implode( ',', $value )
|
||||
), TRUE );
|
||||
}
|
||||
|
||||
// Get the data we need to return
|
||||
$methods = array('inline' => array(), 'email' => array(), 'push' => array());
|
||||
|
||||
foreach( $methods as $method => $methodData )
|
||||
{
|
||||
if( !isset( $options[ $method ] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$option = $options[ $method ];
|
||||
$methods[ $method ]['default'] = isset( $option['default'] ) && \in_array( $method, $option['default'] );
|
||||
$methods[ $method ]['disabled'] = isset( $option['disabled'] ) && \in_array( $method, $option['disabled'] );
|
||||
$methods[ $method ]['member'] = isset( $value[ $method ] );
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => $args['id'],
|
||||
'extension' => $args['extension'],
|
||||
'type' => $type,
|
||||
'name' => $extensionType['title'],
|
||||
'description' => $extensionType['description'],
|
||||
'inline' => $methods['inline'],
|
||||
'email' => $methods['email'],
|
||||
'push' => $methods['push']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Delete attachment mutation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 22 May 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete attachment mutation for GraphQL API
|
||||
*/
|
||||
class _DeleteAttachment
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Delete an attachment";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() ),
|
||||
'editorLocation' => TypeRegistry::string(),
|
||||
'locationId1' => TypeRegistry::int(),
|
||||
'locationId2' => TypeRegistry::int(),
|
||||
'locationId3' => TypeRegistry::string(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::boolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args, $context, $info)
|
||||
{
|
||||
/* Get the attachment */
|
||||
try
|
||||
{
|
||||
$attachment = \IPS\Db::i()->select( '*', 'core_attachments', array( 'attach_id=?', $args['id'] ) )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NO_ATTACHMENT', '2C398/1', 404 );
|
||||
}
|
||||
|
||||
/* Delete the maps - Only do this for attachments that have actually been saved (if they haven't been saved, there's nothing in core_attachments_map for us to delete */
|
||||
if ( isset( $args['editorLocation'] ) and ( isset( $args['locationId1'] ) or isset( $args['locationId2'] ) or isset( $args['locationId3'] ) ) )
|
||||
{
|
||||
$where = array( array( 'location_key=?', $args['editorLocation'] ), array( 'attachment_id=?', $attachment['attach_id'] ) );
|
||||
|
||||
foreach ( range( 1, 3 ) as $i )
|
||||
{
|
||||
if ( isset( $args["locationId{$i}"] ) )
|
||||
{
|
||||
$where[] = array( "id{$i}=?", $args["locationId{$i}"] );
|
||||
}
|
||||
}
|
||||
|
||||
\IPS\Db::i()->delete( 'core_attachments_map', $where );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If the attachment hasn't been claimed yet, it should only be deletable by the person who uploaded it */
|
||||
if( $attachment['attach_member_id'] != \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'CANNOT_DELETE_OTHERS_ATTACHMENTS', '2C398/2', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
/* If there's no other maps, we can delete the attachment itself */
|
||||
$otherMaps = \IPS\Db::i()->select( 'COUNT(*)', 'core_attachments_map', array( 'attachment_id=?', $attachment['attach_id'] ) )->first();
|
||||
if ( !$otherMaps )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_attachments', array( 'attach_id=?', $attachment['attach_id'] ) );
|
||||
try
|
||||
{
|
||||
\IPS\File::get( 'core_Attachment', $attachment['attach_location'] )->delete();
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
if ( $attachment['attach_thumb_location'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( 'core_Attachment', $attachment['attach_thumb_location'] )->delete();
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
/* Return that we're done */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Follow something mutation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 9 Sep 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow something mutation for GraphQL API
|
||||
*/
|
||||
class _Follow
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Follow a node, item or member";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'app' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'area' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() ),
|
||||
'anonymous' => TypeRegistry::nonNull( TypeRegistry::boolean() ),
|
||||
'type' => TypeRegistry::nonNull( TypeRegistry::eNum([
|
||||
'name' => 'core_Follow_followOptions',
|
||||
'values' => ['IMMEDIATE', 'WEEKLY', 'DAILY', 'NONE'],
|
||||
'defaultValue' => 'IMMEDIATE'
|
||||
]))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::follow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
* @todo this is basically copied and pasted from notifications.php which isn't ideal, so we
|
||||
* might want to consider refactoring to abstract this functionality.
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0001/6', 403 );
|
||||
}
|
||||
|
||||
/* Get class */
|
||||
$class = NULL;
|
||||
foreach ( \IPS\Application::load( $args['app'] )->extensions( 'core', 'ContentRouter' ) as $ext )
|
||||
{
|
||||
foreach ( $ext->classes as $classname )
|
||||
{
|
||||
if ( $classname == 'IPS\\' . $args['app'] . '\\' . mb_ucfirst( $args['area'] ) )
|
||||
{
|
||||
$class = $classname;
|
||||
break;
|
||||
}
|
||||
if ( isset( $classname::$containerNodeClass ) and $classname::$containerNodeClass == 'IPS\\' . $args['app'] . '\\' . mb_ucfirst( $args['area'] ) )
|
||||
{
|
||||
$class = $classname::$containerNodeClass;
|
||||
break;
|
||||
}
|
||||
if( isset( $classname::$containerFollowClasses ) )
|
||||
{
|
||||
foreach( $classname::$containerFollowClasses as $followClass )
|
||||
{
|
||||
if( $followClass == 'IPS\\' . $args['app'] . '\\' . mb_ucfirst( $args['area'] ) )
|
||||
{
|
||||
$class = $followClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( $args['app'] == 'core' and $args['area'] == 'member' )
|
||||
{
|
||||
/* You can't follow yourself */
|
||||
if( $args['id'] == \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'CANT_FOLLOW_SELF', 'GQL/0001/1', 403 );
|
||||
}
|
||||
|
||||
/* Following disabled */
|
||||
$member = \IPS\Member::load( $args['id'] );
|
||||
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'CANT_FOLLOW_MEMBER', 'GQL/0001/2', 403 );
|
||||
}
|
||||
|
||||
if( $member->members_bitoptions['pp_setting_moderate_followers'] and !\IPS\Member::loggedIn()->following( 'core', 'member', $member->member_id ) )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'CANT_FOLLOW_MEMBER', 'GQL/0001/3', 403 );
|
||||
}
|
||||
|
||||
$class = 'IPS\\Member';
|
||||
}
|
||||
|
||||
if( $args['app'] == 'core' and $args['area'] == 'club' )
|
||||
{
|
||||
$class = 'IPS\Member\Club';
|
||||
}
|
||||
|
||||
if ( !$class )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_FOUND', 'GQL/0001/4', 404 );
|
||||
}
|
||||
|
||||
/* Get our return info ready */
|
||||
$return = array(
|
||||
'app' => $args['app'],
|
||||
'area' => $args['area'],
|
||||
'id' => $args['id']
|
||||
);
|
||||
|
||||
/* Get thing */
|
||||
$thing = NULL;
|
||||
try
|
||||
{
|
||||
if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
|
||||
{
|
||||
$thing = $class::loadAndCheckPerms( $args['id'] );
|
||||
}
|
||||
elseif ( $class == 'IPS\Member\Club' )
|
||||
{
|
||||
$thing = $class::loadAndCheckPerms( $args['id'] );
|
||||
}
|
||||
elseif ( $class != "IPS\Member" )
|
||||
{
|
||||
$thing = $class::loadAndCheckPerms( $args['id'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$thing = $class::load( $args['id'] );
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_FOUND', 'GQL/0001/5', 404 );
|
||||
}
|
||||
|
||||
/* 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=?', $args['app'], $args['area'], $args['id'], \IPS\Member::loggedIn()->member_id ) )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
$current = FALSE;
|
||||
}
|
||||
|
||||
/* Insert */
|
||||
$save = array(
|
||||
'follow_id' => md5( $args['app'] . ';' . $args['area'] . ';' . $args['id'] . ';' . \IPS\Member::loggedIn()->member_id ),
|
||||
'follow_app' => $args['app'],
|
||||
'follow_area' => $args['area'],
|
||||
'follow_rel_id' => $args['id'],
|
||||
'follow_member_id' => \IPS\Member::loggedIn()->member_id,
|
||||
'follow_is_anon' => $args['anonymous'],
|
||||
'follow_added' => time(),
|
||||
'follow_notify_do' => $args['type'] == 'NONE' ? 0 : 1,
|
||||
'follow_notify_meta' => '',
|
||||
'follow_notify_freq' => ( $class == "IPS\Member" ) ? 'immediate' : mb_strtolower($args['type']),
|
||||
'follow_notify_sent' => 0,
|
||||
'follow_visible' => 1,
|
||||
);
|
||||
if ( $current )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_follow', $save, array( 'follow_id=?', $current['follow_id'] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_follow', $save );
|
||||
}
|
||||
|
||||
/* Also follow all nodes if following club */
|
||||
if( $class == "IPS\Member\Club" )
|
||||
{
|
||||
foreach ( $thing->nodes() as $node )
|
||||
{
|
||||
$itemClass = $node['node_class']::$contentItemClass;
|
||||
$followApp = $itemClass::$application;
|
||||
$followArea = mb_strtolower( mb_substr( $node['node_class'], mb_strrpos( $node['node_class'], '\\' ) + 1 ) );
|
||||
|
||||
$save = array(
|
||||
'follow_id' => md5( $followApp . ';' . $followArea . ';' . $node['node_id'] . ';' . \IPS\Member::loggedIn()->member_id ),
|
||||
'follow_app' => $followApp,
|
||||
'follow_area' => $followArea,
|
||||
'follow_rel_id' => $node['node_id'],
|
||||
'follow_member_id' => \IPS\Member::loggedIn()->member_id,
|
||||
'follow_is_anon' => $args['anonymous'],
|
||||
'follow_added' => time(),
|
||||
'follow_notify_do' => $args['type'] == 'NONE' ? 0 : 1,
|
||||
'follow_notify_meta' => '',
|
||||
'follow_notify_freq' => mb_strtolower($args['type']),
|
||||
'follow_notify_sent' => 0,
|
||||
'follow_visible' => 1,
|
||||
);
|
||||
\IPS\Db::i()->insert( 'core_follow', $save, TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
/* Send notification if following member */
|
||||
if( $class == "IPS\Member" )
|
||||
{
|
||||
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'member_follow', \IPS\Member::loggedIn(), array( \IPS\Member::loggedIn() ) );
|
||||
$notification->recipients->attach( $thing );
|
||||
$notification->send();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
|
||||
{
|
||||
$return = array_merge($return, array(
|
||||
'node' => $thing,
|
||||
'nodeClass' => \get_class( $thing )
|
||||
));
|
||||
}
|
||||
else if( $class == 'IPS\Member' )
|
||||
{
|
||||
$return = array_merge($return, array(
|
||||
'member' => $thing
|
||||
));
|
||||
}
|
||||
else if( $class == 'IPS\Member\Club' )
|
||||
{
|
||||
// @future Support club follows
|
||||
}
|
||||
else
|
||||
{
|
||||
$return = array_merge($return, array(
|
||||
'item' => $thing,
|
||||
'itemClass' => \get_class( $thing )
|
||||
));
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Ignore user mutation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 29 May 2020
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore user mutation for GraphQL API
|
||||
*/
|
||||
class _IgnoreUser
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Ignore a member";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'member' => TypeRegistry::nonNull( TypeRegistry::int() ),
|
||||
'type' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'isIgnoring' => TypeRegistry::nonNull( TypeRegistry::boolean() )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::ignoreOption();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if ( !\in_array( $args['type'], \IPS\core\Ignore::types() ) )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_TYPE', 'GQL/0001/4', 404 );
|
||||
}
|
||||
|
||||
$type = $args['type'];
|
||||
$member = \IPS\Member::load( $args['member'] );
|
||||
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_MEMBER', 'GQL/0001/4', 403 );
|
||||
}
|
||||
|
||||
if ( $member->member_id == \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NO_IGNORE_SELF', 'GQL/0001/4', 403 );
|
||||
}
|
||||
|
||||
if ( !$member->canBeIgnored() )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NO_IGNORE_MEMBER', 'GQL/0001/4', 403 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$ignore = \IPS\core\Ignore::load( $member->member_id, 'ignore_ignore_id', array( 'ignore_owner_id=?', \IPS\Member::loggedIn()->member_id ) );
|
||||
$ignore->$type = $args['isIgnoring'];
|
||||
$ignore->save();
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
$ignore = new \IPS\core\Ignore;
|
||||
$ignore->$type = $args['isIgnoring'];
|
||||
$ignore->owner_id = \IPS\Member::loggedIn()->member_id;
|
||||
$ignore->ignore_id = $member->member_id;
|
||||
$ignore->save();
|
||||
}
|
||||
|
||||
$return = array(
|
||||
'type' => $type,
|
||||
'is_being_ignored' => $args['isIgnoring']
|
||||
);
|
||||
|
||||
\IPS\Member::loggedIn()->members_bitoptions['has_no_ignored_users'] = FALSE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Mark a notification read mutation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 6 Nov 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark notification read mutation for GraphQL API
|
||||
*/
|
||||
class _MarkNotificationRead
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Mark a notification as read";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'id' => TypeRegistry::int()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\Api\GraphQL\TypeRegistry::notification();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
$where = array();
|
||||
$where[] = array( "notification_app IN('" . implode( "','", array_keys( \IPS\Application::enabledApplications() ) ) . "')" );
|
||||
$where[] = array( "member = ?", \IPS\Member::loggedIn()->member_id );
|
||||
|
||||
if( isset( $args['id'] ) && $args['id'] !== NULL )
|
||||
{
|
||||
$where[] = array( "id = ?", \intval( $args['id'] ) );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$row = \IPS\Db::i()->select( '*', 'core_notifications', $where )->first();
|
||||
$notification = \IPS\Notification\Api::constructFromData( $row );
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
if( isset( $args['id'] ) && $args['id'] !== NULL )
|
||||
{
|
||||
// Only throw an error if we were trying to work on a specific notification
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_NOTIFICATION', 'GQL/0003/2', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
\IPS\Db::i()->update( 'core_notifications', array( 'read_time' => time() ), $where );
|
||||
\IPS\Member::loggedIn()->recountNotifications();
|
||||
|
||||
if( $notification )
|
||||
{
|
||||
return array( 'notification' => $notification, 'data' => $notification->getData() );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Add user to PM conversation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 23 Oct 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations\Messenger;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to conversation mutation for GraphQL API
|
||||
*/
|
||||
class _AddConversationUser
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Add a user to a PM conversation";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() ),
|
||||
'memberId' => TypeRegistry::nonNull( TypeRegistry::id() ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\Api\GraphQL\TypeRegistry::messengerConversation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$conversation = \IPS\core\Messenger\Conversation::loadAndCheckPerms( $args['id'] );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NO_MESSAGE', '1F294/2', 400 );
|
||||
}
|
||||
|
||||
// Fetch our new member
|
||||
$member = \IPS\Member::load( $args['memberId'] );
|
||||
|
||||
if( !$member->member_id ){
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_MEMBER', '1F294/2', 403 );
|
||||
}
|
||||
|
||||
// Check member limit
|
||||
if ( \IPS\Member::loggedIn()->group['g_max_mass_pm'] !== -1 AND $conversation->to_count + 1 > \IPS\Member::loggedIn()->group['g_max_mass_pm'] )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'TOO_MANY_RECIPIENTS', '1F294/2', 400 );
|
||||
}
|
||||
|
||||
$maps = $conversation->maps( TRUE );
|
||||
|
||||
// Authorize the member
|
||||
if ( array_key_exists( $member->member_id, $maps ) and !$maps[ $member->member_id ]['map_user_active'] AND !$maps[ $member->member_id ]['map_user_banned'] )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'MEMBER_LEFT', '1F294/2', 403 );
|
||||
}
|
||||
|
||||
if ( !$this->memberCanReceiveNewMessage( $member ) )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'BAD_RECIPIENT', '1F294/2', 403 );
|
||||
}
|
||||
|
||||
//echo 'here';
|
||||
|
||||
$conversation->authorize( $member );
|
||||
$conversation->maps(TRUE); // Need to rebuild participant maps here to reflect this change
|
||||
|
||||
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'private_message_added', $conversation, array( $conversation, \IPS\Member::loggedIn() ) );
|
||||
$notification->recipients->attach( $member );
|
||||
$notification->send();
|
||||
|
||||
return $conversation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the member recieve new messages?
|
||||
*
|
||||
* @param \IPS\Member The member to check
|
||||
* @return boolean
|
||||
*/
|
||||
public function memberCanReceiveNewMessage( $member )
|
||||
{
|
||||
if ( $member->members_disable_pm )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Group can not use messenger */
|
||||
if ( !$member->canAccessModule( \IPS\Application\Module::get( 'core', 'messaging', 'front' ) ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Inbox is full */
|
||||
if ( ( $member->group['g_max_messages'] > 0 AND $member->msg_count_total >= $member->group['g_max_messages'] ) and !\IPS\Member::loggedIn()->group['gbw_pm_override_inbox_full'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Is being ignored */
|
||||
if ( $member->isIgnoring( \IPS\Member::loggedIn(), 'messages' ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Leave a PM conversation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 23 Oct 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations\Messenger;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave conversation mutation for GraphQL API
|
||||
*/
|
||||
class _LeaveConversation
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Leave a PM conversation";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\Api\GraphQL\TypeRegistry::messengerConversation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$conversation = \IPS\core\Messenger\Conversation::loadAndCheckPerms( $args['id'] );
|
||||
$conversation->deauthorize( \IPS\Member::loggedIn() );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NO_MESSAGE', '1F294/2', 400 );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Remove a user from a conversation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 23 Oct 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations\Messenger;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove user from conversation mutation for GraphQL API
|
||||
*/
|
||||
class _RemoveConversationUser
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Leave a PM conversation";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() ),
|
||||
'memberId' => TypeRegistry::id()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\Api\GraphQL\TypeRegistry::messengerConversation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$conversation = \IPS\core\Messenger\Conversation::loadAndCheckPerms( $args['id'] );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NO_MESSAGE', '1F294/2', 400 );
|
||||
}
|
||||
|
||||
if( $conversation->starter_id !== \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_OWNER', 'GQL/0003/1', 403 );
|
||||
}
|
||||
|
||||
$conversation->deauthorize( \IPS\Member::load( $args['memberId'] ), TRUE );
|
||||
$conversation->maps(TRUE); // Need to rebuild participant maps here to reflect this change
|
||||
|
||||
return $conversation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: End a session for the user
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 02 Apr 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a push token for a specific user
|
||||
*/
|
||||
class _SessionEnd
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "End a user session";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'token' => TypeRegistry::string()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\Api\GraphQL\TypeRegistry::member();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
* @todo this is basically copied and pasted from notifications.php which isn't ideal, so we
|
||||
* might want to consider refactoring to abstract this functionality.
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0001/6', 403 );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'member_id=? AND access_token=?', \IPS\Member::loggedIn()->member_id, \IPS\Dispatcher::i()->accessToken['access_token'] ) );
|
||||
if ( isset( \IPS\Dispatcher::i()->accessToken['device_key'] ) )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'member_id=? AND device_key=?', \IPS\Member::loggedIn()->member_id, \IPS\Dispatcher::i()->accessToken['device_key'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $args['token'] ) and $args['token'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::ips('mobile/signout')->request()->login( \IPS\Settings::i()->mobile_app_id, \IPS\Settings::i()->mobile_app_secret )->post( array(
|
||||
'member' => \IPS\Member::loggedIn()->member_id,
|
||||
'token' => $args['token']
|
||||
) )->decodeJson();
|
||||
|
||||
if ( $response->httpResponseCode != 200 )
|
||||
{
|
||||
throw new \DomainException( (string) $response );
|
||||
}
|
||||
|
||||
if ( !$response['remainingDevices'] )
|
||||
{
|
||||
\IPS\Member::loggedIn()->members_bitoptions['mobile_notifications'] = FALSE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'mobile-push-notifications' );
|
||||
}
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Start a session for a user
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 20 Mar 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a push token for a specific user
|
||||
*/
|
||||
class _SessionStart
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Start a session for an authenticated user";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'token' => TypeRegistry::string()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\Api\GraphQL\TypeRegistry::member();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
* @todo this is basically copied and pasted from notifications.php which isn't ideal, so we
|
||||
* might want to consider refactoring to abstract this functionality.
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0001/6', 403 );
|
||||
}
|
||||
|
||||
if ( isset( $args['token'] ) and $args['token'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Register the token with the backend */
|
||||
$response = \IPS\Http\Url::ips('mobile/signin')->request()->login( \IPS\Settings::i()->mobile_app_id, \IPS\Settings::i()->mobile_app_secret )->post( array(
|
||||
'member' => \IPS\Member::loggedIn()->member_id,
|
||||
'token' => $args['token']
|
||||
) );
|
||||
if ( !\in_array( $response->httpResponseCode, array( 200, 201 ) ) )
|
||||
{
|
||||
throw new \DomainException( (string) $response );
|
||||
}
|
||||
|
||||
/* If we didn't already have notifications enabled, enable them and set the defaults */
|
||||
if ( !\IPS\Member::loggedIn()->members_bitoptions['mobile_notifications'] )
|
||||
{
|
||||
\IPS\Member::loggedIn()->members_bitoptions['mobile_notifications'] = TRUE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
|
||||
$notificationPreferences = \IPS\Db::i()->select( '*', 'core_notification_preferences', array( 'member_id=?', \IPS\Member::loggedIn()->member_id ) );
|
||||
if ( \count( $notificationPreferences ) )
|
||||
{
|
||||
$keysToAddPushTo = array();
|
||||
|
||||
foreach ( $notificationPreferences as $row )
|
||||
{
|
||||
if ( \in_array( 'inline', explode( ',', $row['preference'] ) ) )
|
||||
{
|
||||
$keysToAddPushTo[] = $row['notification_key'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $keysToAddPushTo ) )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_notification_preferences', "`preference` = IF( `preference` = '', 'push', CONCAT_WS( ',', `preference`, 'push' ) )", array( array( 'member_id=?', \IPS\Member::loggedIn()->member_id ), array( \IPS\Db::i()->in( 'notification_key', $keysToAddPushTo ) ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'mobile-push-notifications' );
|
||||
}
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Unfollow something mutation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 9 Sep 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow something mutation for GraphQL API
|
||||
*/
|
||||
class _Unfollow
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Unfollow a node, item or member";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'app' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'area' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() ),
|
||||
'followID' => TypeRegistry::nonNull( TypeRegistry::id() )
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::follow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
* @todo this is basically copied and pasted from notifications.php which isn't ideal, so we
|
||||
* might want to consider refactoring to abstract this functionality.
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\forums\Forum
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_LOGGED_IN', 'GQL/0002/6', 403 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$follow = \IPS\Db::i()->select( '*', 'core_follow', array( 'follow_id=? AND follow_member_id=?', $args['followID'], \IPS\Member::loggedIn()->member_id ) )->first();
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) {}
|
||||
|
||||
\IPS\Db::i()->delete( 'core_follow', array( 'follow_id=? AND follow_member_id=?', $args['followID'], \IPS\Member::loggedIn()->member_id ) );
|
||||
|
||||
/* Get class */
|
||||
if( $args['app'] == 'core' and $args['area'] == 'member' )
|
||||
{
|
||||
$class = 'IPS\\Member';
|
||||
}
|
||||
elseif( $args['app'] == 'core' and $args['area'] == 'club' )
|
||||
{
|
||||
$class = 'IPS\\Member\Club';
|
||||
}
|
||||
else
|
||||
{
|
||||
$class = NULL;
|
||||
foreach ( \IPS\Application::load( $args['app'] )->extensions( 'core', 'ContentRouter' ) as $ext )
|
||||
{
|
||||
foreach ( $ext->classes as $classname )
|
||||
{
|
||||
if ( $classname == 'IPS\\' . $args['app'] . '\\' . mb_ucfirst( $args['area'] ) )
|
||||
{
|
||||
$class = $classname;
|
||||
break;
|
||||
}
|
||||
if ( isset( $classname::$containerNodeClass ) and $classname::$containerNodeClass == 'IPS\\' . $args['app'] . '\\' . mb_ucfirst( $args['area'] ) )
|
||||
{
|
||||
$class = $classname::$containerNodeClass;
|
||||
break;
|
||||
}
|
||||
if( isset( $classname::$containerFollowClasses ) )
|
||||
{
|
||||
foreach( $classname::$containerFollowClasses as $followClass )
|
||||
{
|
||||
if( $followClass == 'IPS\\' . $args['app'] . '\\' . mb_ucfirst( $args['area'] ) )
|
||||
{
|
||||
$class = $followClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get our return info ready */
|
||||
$return = array(
|
||||
'app' => $args['app'],
|
||||
'area' => $args['area'],
|
||||
'id' => $args['id']
|
||||
);
|
||||
|
||||
/* Get thing */
|
||||
$thing = NULL;
|
||||
|
||||
try
|
||||
{
|
||||
if ( $class != "IPS\Member" )
|
||||
{
|
||||
$thing = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$thing = $class::load( $follow['follow_rel_id'] );
|
||||
}
|
||||
|
||||
/* Unfollow club areas */
|
||||
if( $class == "IPS\Member\Club" )
|
||||
{
|
||||
foreach ( $thing->nodes() as $node )
|
||||
{
|
||||
$itemClass = $node['node_class']::$contentItemClass;
|
||||
$followApp = $itemClass::$application;
|
||||
$followArea = mb_strtolower( mb_substr( $node['node_class'], mb_strrpos( $node['node_class'], '\\' ) + 1 ) );
|
||||
|
||||
\IPS\Db::i()->delete( 'core_follow', array( 'follow_id=? AND follow_member_id=?', md5( $followApp . ';' . $followArea . ';' . $node['node_id'] . ';' . \IPS\Member::loggedIn()->member_id ), \IPS\Member::loggedIn()->member_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
|
||||
{
|
||||
$return = array_merge($return, array(
|
||||
'node' => $thing,
|
||||
'nodeClass' => \get_class( $thing )
|
||||
));
|
||||
}
|
||||
else if( $class == 'IPS\Member' )
|
||||
{
|
||||
$return = array_merge($return, array(
|
||||
'member' => $thing
|
||||
));
|
||||
}
|
||||
else if( $class == 'IPS\Member\Club' )
|
||||
{
|
||||
// @future Support clubs
|
||||
}
|
||||
else
|
||||
{
|
||||
$return = array_merge($return, array(
|
||||
'item' => $thing,
|
||||
'itemClass' => \get_class( $thing )
|
||||
));
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Upload attachment mutation
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 22 May 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Mutations;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload attachment mutation for GraphQL API
|
||||
*/
|
||||
class _UploadAttachment
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Upload a file to be an attachment";
|
||||
|
||||
/*
|
||||
* Mutation arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return [
|
||||
'name' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'contents' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'postKey' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
'chunk' => TypeRegistry::int(),
|
||||
'totalChunks' => TypeRegistry::int(),
|
||||
'ref' => TypeRegistry::nonNull( TypeRegistry::string() ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutation return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return new UnionType([
|
||||
'name' => 'core_UploadAttachment',
|
||||
'types' => [
|
||||
\IPS\core\api\GraphQL\TypeRegistry::attachment(),
|
||||
\IPS\core\api\GraphQL\TypeRegistry::uploadProgress(),
|
||||
],
|
||||
'resolveType' => function ($obj) {
|
||||
if ( isset( $obj['ref'] ) )
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::uploadProgress();
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::attachment();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this mutation
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return array
|
||||
*/
|
||||
public function resolve($val, $args, $context, $info)
|
||||
{
|
||||
$storageClass = \IPS\File::getClass( 'core_Attachment' );
|
||||
$contents = base64_decode( $args['contents'] );
|
||||
|
||||
/* Check allowed types */
|
||||
$ext = mb_substr( $args['name'], mb_strrpos( $args['name'], '.' ) + 1 );
|
||||
if( $allowedFileTypes = \IPS\Helpers\Form\Editor::allowedFileExtensions() )
|
||||
{
|
||||
if( !\in_array( mb_strtolower( $ext ), array_map( 'mb_strtolower', $allowedFileTypes ) ) )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'FILE_TYPE_NOT_ALLOWED', '2C399/1', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Chunking? */
|
||||
if ( $storageClass::$supportsChunking and isset( $args['chunk'] ) )
|
||||
{
|
||||
/* If this is the FIRST chunk, start the process */
|
||||
if ( $args['chunk'] === 1 )
|
||||
{
|
||||
$ref = $storageClass->chunkInit( $args['name'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$ref = json_decode( $args['ref'] );
|
||||
}
|
||||
|
||||
/* Process this chunk */
|
||||
$ref = $storageClass->chunkProcess( $ref, $contents, --$args['chunk'], TRUE );
|
||||
|
||||
/* If this is the LAST chunk, finish the process */
|
||||
if ( $args['chunk'] === $args['totalChunks'] )
|
||||
{
|
||||
$url = $storageClass->chunkFinish( $ref );
|
||||
$file = \IPS\File::get( 'core_Attachment', $url );
|
||||
|
||||
/* If it's got an image extension, check it's actually a valid image */
|
||||
if ( \in_array( $ext, \IPS\Image::$imageExtensions ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$file->getImageDimensions();
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NOT_VALID_IMAGE', '2C399/2', 403 );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're continuing to the next chunk, return an UploadProgress object containing the ref
|
||||
$ref = json_encode( $ref );
|
||||
|
||||
return array(
|
||||
'name' => $args['name'],
|
||||
'ref' => $args['ref']
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If it's got an image extension, check it's actually a valid image */
|
||||
if ( \in_array( $ext, \IPS\Image::$imageExtensions ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$image = \IPS\Image::create( $contents );
|
||||
}
|
||||
catch ( \InvalidArgumentException $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( $e->getMessage(), '2C399/2', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the file */
|
||||
try
|
||||
{
|
||||
$file = \IPS\File::create( 'core_Attachment', $args['name'], $contents );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'FILE_CREATION_FAILED', '2C399/3', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Make it into an attachment */
|
||||
$attachment = $file->makeAttachment( $args['postKey'], \IPS\Member::loggedIn() );
|
||||
return $attachment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: ActiveUsers query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* OurPicks query for GraphQL API
|
||||
*/
|
||||
class _ActiveUsers
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns active user information";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::activeUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @return \IPS\core\Stream
|
||||
*/
|
||||
public function resolve($val)
|
||||
{
|
||||
if ( !\IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'online', 'front' ) ) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// There's no real value here, we're just passing on to the next resolver in the chain
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Club query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Club query for GraphQL API
|
||||
*/
|
||||
class _Club
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns a club";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args() {
|
||||
return array(
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::club();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Member\Club
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
return \IPS\Member\Club::loadAndCheckPerms( $args['id'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Content query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Me query for GraphQL API
|
||||
*/
|
||||
class _Content
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Return a generic piece of content";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'url' => TypeRegistry::nonNull( TypeRegistry::string() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\Content\Api\GraphQL\TypeRegistry::content();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Member|null
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $args['url'] );
|
||||
|
||||
if ( !isset( $url->hiddenQueryString['app'] ) )
|
||||
{
|
||||
throw new Exception('Not a valid URL');
|
||||
}
|
||||
|
||||
foreach ( \IPS\Application::load( $url->hiddenQueryString['app'] )->extensions( 'core', 'ContentRouter' ) as $ext )
|
||||
{
|
||||
foreach ( $ext->classes as $class )
|
||||
{
|
||||
try
|
||||
{
|
||||
$object = $class::loadFromUrl( $url );
|
||||
|
||||
if ( isset( $url->queryString['do'] ) and $url->queryString['do'] === 'findComment' )
|
||||
{
|
||||
$commentClass = $object::$commentClass;
|
||||
$object = $commentClass::load( $url->queryString['comment'] );
|
||||
}
|
||||
elseif ( isset( $url->queryString['do'] ) and $url->queryString['do'] === 'findReview' )
|
||||
{
|
||||
$reviewClass = $object::$reviewClass;
|
||||
$object = $reviewClass::load( $url->queryString['review'] );
|
||||
}
|
||||
|
||||
if ( !$object->canView( \IPS\Member::loggedIn() ) )
|
||||
{
|
||||
// No permission
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Group query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group query for GraphQL API
|
||||
*/
|
||||
class _Group
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns a member group";
|
||||
|
||||
/*
|
||||
* @brief Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::group();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Member\Group
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
return \IPS\Member\Group::load( $args['id'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Language query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Me query for GraphQL API
|
||||
*/
|
||||
class _Language
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Return language data";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'id' => TypeRegistry::id()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::language();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Member|null
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
if( isset( $args['id'] ) ){
|
||||
return \IPS\Lang::load( $args['id'] );
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Login handler query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 30 Oct 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login handler query for GraphQL API
|
||||
*/
|
||||
class _LoginHandlers
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Return login handler data";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::login() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Member|null
|
||||
*/
|
||||
public function resolve($val, $args)
|
||||
{
|
||||
$login = new \IPS\Login;
|
||||
return $login->buttonMethods();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: 'Me' query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Me query for GraphQL API
|
||||
*/
|
||||
class _Me
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Return the logged-in member";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::member();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Member|null
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
return \IPS\Member::loggedIn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Member query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Member query for GraphQL API
|
||||
*/
|
||||
class _Member
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Return a member";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'id' => TypeRegistry::id(),
|
||||
'loggedIn' => TypeRegistry::boolean()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::member();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Member|null
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
if( isset( $args['loggedIn'] ) ){
|
||||
return \IPS\Member::loggedIn();
|
||||
} elseif ( isset( $args['id'] ) ){
|
||||
return \IPS\Member::load( $args['id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Messenger conversation query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 25 Sep 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Messenger conversation query for GraphQL API
|
||||
*/
|
||||
class _MessengerConversation
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns a messenger conversation";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'id' => TypeRegistry::nonNull( TypeRegistry::id() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::messengerConversation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Messenger\Conversation
|
||||
*/
|
||||
public function resolve($val, $args, $context, $info)
|
||||
{
|
||||
try
|
||||
{
|
||||
$conversation = \IPS\core\Messenger\Conversation::loadAndCheckPerms( $args['id'] );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'NO_MESSAGE', '1F294/2', 400 );
|
||||
}
|
||||
|
||||
return $conversation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Messenger conversation squery
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 25 Sep 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Messenger conversations query for GraphQL API
|
||||
*/
|
||||
class _MessengerConversations
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns a list of messenger conversations";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'folder' => TypeRegistry::id(),
|
||||
'filter' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_messengerConversations_filter',
|
||||
'values' => ['MINE', 'NOT_MINE', 'READ', 'NOT_READ']
|
||||
]),
|
||||
'defaultValue' => NULL
|
||||
],
|
||||
'search' => TypeRegistry::string(),
|
||||
'searchIn' => [
|
||||
'type' => TypeRegistry::listOf(
|
||||
TypeRegistry::eNum([
|
||||
'name' => 'core_messengerConversations_searchIn',
|
||||
'description' => 'Fields on which a conversation search can be applied',
|
||||
'values' => ['TOPIC', 'POST', 'SENDER', 'RECIPIENT']
|
||||
])
|
||||
)
|
||||
],
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
],
|
||||
'orderBy' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_messengerConversations_orderBy',
|
||||
'description' => 'Fields on which conversations can be sorted',
|
||||
'values' => \IPS\core\api\GraphQL\Types\MessengerConversationType::getOrderByOptions()
|
||||
]),
|
||||
'defaultValue' => 'last_post_time'
|
||||
],
|
||||
'orderDir' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_messengerConversations_orderDir',
|
||||
'description' => 'Directions in which items can be sorted',
|
||||
'values' => [ 'ASC', 'DESC' ]
|
||||
]),
|
||||
'defaultValue' => 'DESC'
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::messengerConversation() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Messenger\Conversation
|
||||
*/
|
||||
public function resolve($val, $args, $context, $info)
|
||||
{
|
||||
// Start to build the query to fetch messages
|
||||
$where = array( array( 'core_message_topic_user_map.map_user_id=? AND core_message_topic_user_map.map_user_active=1', \IPS\Member::loggedIn()->member_id ) );
|
||||
$orderBy = 'mt_' . $args['orderBy'];
|
||||
$offset = max( $args['limit'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
$iterator = NULL;
|
||||
|
||||
// Filters
|
||||
if( !\is_null( $args['filter'] ) )
|
||||
{
|
||||
switch ($args['filter'] ) {
|
||||
case "MINE":
|
||||
$where[] = array( 'core_message_topic_user_map.map_is_starter=1' );
|
||||
break;
|
||||
case "NOT_MINE":
|
||||
$where[] = array( 'core_message_topic_user_map.map_is_starter=0' );
|
||||
break;
|
||||
case "READ":
|
||||
$where[] = array( 'core_message_topic_user_map.map_has_unread=0' );
|
||||
break;
|
||||
case "NOT_READ":
|
||||
$where[] = array( 'core_message_topic_user_map.map_has_unread=1' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Folder
|
||||
if ( !\is_null( $args['folder'] ) )
|
||||
{
|
||||
$folderObj = \IPS\core\api\GraphQL\TypeRegistry::messengerFolder();
|
||||
$folders = $folderObj->getMemberFolders();
|
||||
|
||||
if( isset( $folders[ $args['folder'] ] ) )
|
||||
{
|
||||
$where[] = array( 'core_message_topic_user_map.map_folder_id=?', $args['folder'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Search
|
||||
if( !\is_null( $args['search'] ) )
|
||||
{
|
||||
$subQuery = \IPS\Db::i()->select( 'map_topic_id', array( 'core_message_topic_user_map', 'core_message_topic_user_map' ), $where );
|
||||
$where = array( array( 'core_message_posts.msg_topic_id IN (?)', $subQuery ) );
|
||||
$query = array();
|
||||
$prefix = \IPS\Db::i()->prefix;
|
||||
|
||||
if ( !\is_null( $args['searchIn'] ) )
|
||||
{
|
||||
if ( \in_array( 'TOPIC', $args['searchIn'] ) )
|
||||
{
|
||||
$query[] = \IPS\Content\Search\Mysql\Query::matchClause( "core_message_topics.mt_title", $args['search'], '+', FALSE );
|
||||
}
|
||||
if ( \in_array( 'POST', $args['searchIn'] ) )
|
||||
{
|
||||
$query[] = \IPS\Content\Search\Mysql\Query::matchClause( "core_message_posts.msg_post", $args['search'], '+', FALSE );
|
||||
}
|
||||
if ( \in_array( 'RECIPIENT', $args['searchIn'] ) )
|
||||
{
|
||||
$query[] = "core_message_posts.msg_topic_id IN ( SELECT sender_map.map_topic_id FROM {$prefix}core_message_topic_user_map AS sender_map WHERE sender_map.map_is_starter=1 AND sender_map.map_user_id IN ( SELECT member_id FROM {$prefix}core_members AS sm WHERE name LIKE '" . \IPS\Db::i()->escape_string( $args['search'] ) . "%' ) )";
|
||||
}
|
||||
if ( \in_array( 'SENDER', $args['searchIn'] ) )
|
||||
{
|
||||
$query[] = "core_message_posts.msg_topic_id IN ( SELECT receiver_map.map_topic_id FROM {$prefix}core_message_topic_user_map AS receiver_map WHERE receiver_map.map_is_starter=0 AND receiver_map.map_user_id IN ( SELECT member_id FROM {$prefix}core_members AS rm WHERE name LIKE '" . \IPS\Db::i()->escape_string( $args['search'] ) . "%') )";
|
||||
}
|
||||
|
||||
if ( \count( $query ) )
|
||||
{
|
||||
$where[] = array( '(' . implode( ' OR ', $query ) . ')' );
|
||||
}
|
||||
}
|
||||
|
||||
/* Get a count */
|
||||
try
|
||||
{
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_message_posts', $where, NULL, NULL, 'msg_topic_id' )
|
||||
->join(
|
||||
'core_message_topics',
|
||||
'core_message_posts.msg_topic_id=core_message_topics.mt_id'
|
||||
)
|
||||
->join(
|
||||
'core_message_topic_user_map',
|
||||
'core_message_topic_user_map.map_topic_id=core_message_topics.mt_id'
|
||||
)
|
||||
->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
/* Performance: if count is 0, don't bother selecting ... it's a wasted query */
|
||||
if( $count )
|
||||
{
|
||||
/* Because of strict group by, we first need to select the ids, then grab those topics */
|
||||
$iterator = \IPS\Db::i()->select(
|
||||
'core_message_posts.msg_topic_id',
|
||||
'core_message_posts',
|
||||
$where,
|
||||
$orderBy . ' ' . $args['orderDir'],
|
||||
array( $offset, $limit ),
|
||||
array( 'msg_topic_id', $orderBy )
|
||||
)->join(
|
||||
'core_message_topics',
|
||||
'core_message_posts.msg_topic_id=core_message_topics.mt_id'
|
||||
)->join(
|
||||
'core_message_topic_user_map',
|
||||
'core_message_topic_user_map.map_topic_id=core_message_topics.mt_id'
|
||||
);
|
||||
|
||||
/* Get iterator */
|
||||
$iterator = \IPS\Db::i()->select(
|
||||
'core_message_topic_user_map.*, core_message_topics.*',
|
||||
'core_message_topic_user_map',
|
||||
array( 'map_topic_id IN(' . implode( ',', iterator_to_array( $iterator ) ) . ')' ),
|
||||
$orderBy . ' ' . $args['orderDir']
|
||||
)->join(
|
||||
'core_message_topics',
|
||||
'core_message_topic_user_map.map_topic_id=core_message_topics.mt_id'
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Get iterator */
|
||||
$iterator = \IPS\Db::i()->select(
|
||||
'core_message_topic_user_map.*, core_message_topics.*',
|
||||
'core_message_topic_user_map',
|
||||
$where,
|
||||
$orderBy . ' ' . $args['orderDir'],
|
||||
array( $offset, $limit )
|
||||
)->join(
|
||||
'core_message_topics',
|
||||
'core_message_topic_user_map.map_topic_id=core_message_topics.mt_id'
|
||||
);
|
||||
}
|
||||
|
||||
if( $iterator )
|
||||
{
|
||||
return new \IPS\Patterns\ActiveRecordIterator( $iterator, 'IPS\core\Messenger\Conversation' );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Messenger conversation query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 25 Sep 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Messenger folders query for GraphQL API
|
||||
*/
|
||||
class _MessengerFolders
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns the member's messenger folders";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::messengerFolder() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Messenger\Conversation
|
||||
*/
|
||||
public function resolve($val, $args, $context, $info)
|
||||
{
|
||||
if( !\IPS\Member::loggedIn()->member_id || \IPS\Member::loggedIn()->members_disable_pm )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$folderObj = \IPS\core\api\GraphQL\TypeRegistry::messengerFolder();
|
||||
return $folderObj->getMemberFolders();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Mobile menu query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 12 June 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mobile menu query for GraphQL API
|
||||
*/
|
||||
class _MobileMenu
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns details of the mobile app menu";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::mobileMenu() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return array
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
$return = array();
|
||||
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_mobileapp_menu', NULL, 'position' ) as $item )
|
||||
{
|
||||
if ( \IPS\Application::appIsEnabled( $item['app'] ) )
|
||||
{
|
||||
$class = 'IPS\\' . $item['app'] . '\extensions\core\MobileNavigation\\' . $item['extension'];
|
||||
if ( class_exists( $class ) )
|
||||
{
|
||||
$object = new $class( json_decode( $item['config'], TRUE ), $item['id'], $item['permissions'] );
|
||||
if ( $object->canView() )
|
||||
{
|
||||
$return[ $item['id'] ] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Notification types query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 30 Jan 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationTypes query for GraphQL API
|
||||
*/
|
||||
class _NotificationTypes
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns available notification types";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::notificationType() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Stream
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
//$defaultConfiguration = \IPS\Notification::defaultConfiguration();
|
||||
//$memberConfiguration = $member->notificationsConfiguration();
|
||||
$notificationTypes = array();
|
||||
|
||||
$extensions = \IPS\Application::allExtensions( 'core', 'Notifications' );
|
||||
$availableOptions = array();
|
||||
|
||||
foreach( $extensions as $extensionKey => $extension )
|
||||
{
|
||||
$extensionOptions = \IPS\Notification::availableOptions( \IPS\Member::loggedIn(), $extension );
|
||||
|
||||
foreach( $extensionOptions as $key => $option )
|
||||
{
|
||||
// For now, only return 'standard' types, which are the email/inline/push options
|
||||
if( $option['type'] !== 'standard' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$methods = array('inline' => array(), 'email' => array(), 'push' => array());
|
||||
|
||||
foreach( $methods as $method => $methodData )
|
||||
{
|
||||
if( !isset( $option['options'][ $method ] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$methods[ $method ]['default'] = isset( $option['default'] ) && \in_array( $method, $option['default'] );
|
||||
$methods[ $method ]['disabled'] = isset( $option['disabled'] ) && \in_array( $method, $option['disabled'] );
|
||||
$methods[ $method ]['member'] = isset( $option['options'][ $method ] ) && $option['options'][ $method ]['value'] == TRUE;
|
||||
}
|
||||
|
||||
$notificationTypes[] = array(
|
||||
'id' => $extensionKey === $key ? $extensionKey : $extensionKey . '_' . $key,
|
||||
'extension' => $extensionKey,
|
||||
'type' => $key,
|
||||
'name' => $option['title'],
|
||||
'description' => $option['description'],
|
||||
'inline' => $methods['inline'],
|
||||
'email' => $methods['email'],
|
||||
'push' => $methods['push']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $notificationTypes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: OurPicks query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* OurPicks query for GraphQL API
|
||||
*/
|
||||
class _OurPicks
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns promoted items";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'count' => TypeRegistry::int()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::promotedItem() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Stream
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
$limit = 7;
|
||||
if( isset( $args['count'] ) && \is_int( $args['count'] ) )
|
||||
{
|
||||
$limit = $args['count'];
|
||||
}
|
||||
|
||||
$items = \IPS\core\Promote::internalStream( $limit );
|
||||
if( !\count( $items ) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: PopularContributors query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 11 Feb 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* PopularContributors query for GraphQL API
|
||||
*/
|
||||
class _PopularContributors
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns popular contributors";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 5
|
||||
],
|
||||
'period' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_PopularContributors_period',
|
||||
'values' => ['WEEK', 'MONTH', 'YEAR', 'ALL']
|
||||
]),
|
||||
'defaultValue' => 'WEEK'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::popularContributor() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Stream
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
/* How many? */
|
||||
$limit = min( $args['limit'], 25 );
|
||||
|
||||
/* What timeframe? */
|
||||
$where = array( array( 'member_received > 0' ) );
|
||||
$timeframe = 'all';
|
||||
if ( $args['period'] !== 'ALL' )
|
||||
{
|
||||
switch ( $args['period'] )
|
||||
{
|
||||
case 'WEEK':
|
||||
$where[] = array( 'rep_date>' . \IPS\DateTime::create()->sub( new \DateInterval( 'P1W' ) )->getTimestamp() );
|
||||
break;
|
||||
case 'MONTH':
|
||||
$where[] = array( 'rep_date>' . \IPS\DateTime::create()->sub( new \DateInterval( 'P1M' ) )->getTimestamp() );
|
||||
break;
|
||||
case 'YEAR':
|
||||
$where[] = array( 'rep_date>' . \IPS\DateTime::create()->sub( new \DateInterval( 'P1Y' ) )->getTimestamp() );
|
||||
break;
|
||||
}
|
||||
|
||||
$innerQuery = \IPS\Db::i()->select( 'core_reputation_index.member_received as member, SUM(rep_rating) as rep', 'core_reputation_index', $where, NULL, NULL, 'member' );
|
||||
$topContributors = iterator_to_array( \IPS\Db::i()->select( 'member, rep', array( $innerQuery, 'in' ), NULL, 'rep DESC', $limit )->setKeyField('member')->setValueField('rep') );
|
||||
}
|
||||
else
|
||||
{
|
||||
$topContributors = iterator_to_array( \IPS\Db::i()->select( 'member_id as member, pp_reputation_points as rep', 'core_members', array( 'pp_reputation_points > 0' ), 'rep DESC', $limit )->setKeyField('member')->setValueField('rep') );
|
||||
}
|
||||
|
||||
/* Contruct their data */
|
||||
/* The PopularContributorsType will call ::load on the member ID to get the object */
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_members', \IPS\Db::i()->in( 'member_id', array_keys( $topContributors ) ) ) as $member )
|
||||
{
|
||||
\IPS\Member::constructFromData( $member );
|
||||
}
|
||||
|
||||
$output = array();
|
||||
|
||||
foreach ( $topContributors as $memberID => $rep )
|
||||
{
|
||||
$output[] = array(
|
||||
'rep' => $rep,
|
||||
'member_id' => $memberID
|
||||
);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Search query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 22 Sep 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search query for GraphQL API
|
||||
*/
|
||||
class _Search
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns search results";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'term' => TypeRegistry::string(),
|
||||
'type' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_search_types_input',
|
||||
'description' => "The available search types",
|
||||
'values' => array_merge( array('core_members'), array_keys( \IPS\core\modules\front\search\search::contentTypes() ) )
|
||||
]),
|
||||
],
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
],
|
||||
'orderBy' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_search_order_by',
|
||||
'description' => 'Fields on which reuslts can be sorted',
|
||||
'values' => [ 'newest', 'relevancy', 'joined', 'name', 'member_posts', 'pp_reputation_points' ]
|
||||
])
|
||||
],
|
||||
'orderDir' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_search_order_dir',
|
||||
'description' => 'Directions in which reuslts can be sorted',
|
||||
'values' => [ 'ASC', 'DESC' ]
|
||||
]),
|
||||
'defaultValue' => 'DESC'
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::search();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Stream
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
$where = array();
|
||||
$returnObject = array();
|
||||
$offset = max( $args['offset'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
|
||||
// Member search
|
||||
if ( isset( $args['type'] ) and $args['type'] === 'core_members' and \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'members', 'front' ) ) )
|
||||
{
|
||||
if ( $args['term'] )
|
||||
{
|
||||
$where = array( array( 'LOWER(core_members.name) LIKE ?', '%' . mb_strtolower( trim( $args['term'] ) ) . '%' ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$where = array( array( 'core_members.name<>?', '' ) );
|
||||
}
|
||||
|
||||
// Ordering
|
||||
$orderDir = isset( $args['orderDir'] ) ? $args['orderDir'] : 'DESC';
|
||||
$orderBy = 'name';
|
||||
|
||||
if( isset( $args['orderBy'] ) && \in_array( $args['orderBy'], array( 'joined', 'name', 'member_posts', 'pp_reputation_points' ) ) )
|
||||
{
|
||||
$orderBy = $args['orderBy'];
|
||||
}
|
||||
|
||||
$order = $orderBy . ' ' . $orderDir;
|
||||
|
||||
// Get results
|
||||
$select = \IPS\Db::i()->select( 'COUNT(*)', 'core_members', $where );
|
||||
$select->join( 'core_pfields_content', 'core_pfields_content.member_id=core_members.member_id' );
|
||||
$returnObject['count'] = $select->first();
|
||||
|
||||
$select = \IPS\Db::i()->select( 'core_members.*', 'core_members', $where, $order, array( $offset, $limit ) );
|
||||
$select->join( 'core_pfields_content', 'core_pfields_content.member_id=core_members.member_id' );
|
||||
|
||||
$returnObject['results'] = new \IPS\Patterns\ActiveRecordIterator( $select, 'IPS\Member' );
|
||||
|
||||
return $returnObject;
|
||||
}
|
||||
|
||||
// Content search
|
||||
$query = \IPS\Content\Search\Query::init();
|
||||
$types = \IPS\core\modules\front\search\search::contentTypes();
|
||||
|
||||
if( isset( $args['type'] ) && !empty( $args['type'] ) )
|
||||
{
|
||||
$class = $types[ $args['type'] ];
|
||||
$filter = \IPS\Content\Search\ContentFilter::init( $class );
|
||||
$query->filterByContent( array( $filter ) );
|
||||
}
|
||||
|
||||
$orderBy = $query->getDefaultSortMethod();
|
||||
|
||||
// Ordering
|
||||
if( isset( $args['orderBy'] ) )
|
||||
{
|
||||
switch( $args['orderBy'] )
|
||||
{
|
||||
case 'newest':
|
||||
$query->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_CREATED );
|
||||
break;
|
||||
case 'relevancy':
|
||||
$query->setOrder( \IPS\Content\Search\Query::ORDER_RELEVANCY );
|
||||
break;
|
||||
default:
|
||||
$query->setOrder( $orderBy );
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$query->setOrder( $orderBy );
|
||||
}
|
||||
|
||||
// Get page
|
||||
// We don't know the count at this stage, so figure out the page number from
|
||||
// our offset/limit
|
||||
$page = 1;
|
||||
|
||||
if( $offset > 0 )
|
||||
{
|
||||
$page = floor( $offset / $limit ) + 1;
|
||||
}
|
||||
|
||||
$query->setLimit( $limit )->setPage( $page );
|
||||
|
||||
/* Run query */
|
||||
$returnObject['results'] = $query->search(
|
||||
isset( $args['term'] ) ? ( $args['term'] ) : NULL,
|
||||
NULL,
|
||||
\IPS\Content\Search\Query::TERM_AND_TAGS + \IPS\Content\Search\Query::TAGS_MATCH_ITEMS_ONLY,
|
||||
NULL
|
||||
);
|
||||
$returnObject['count'] = $returnObject['results']->count( TRUE );
|
||||
|
||||
return $returnObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Settings query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings query for GraphQL API
|
||||
*/
|
||||
class _Settings
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns values of system settings (subject to the setting being exposed by the API)";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\Settings
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
return \IPS\Settings::i();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Stream query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream query for GraphQL API
|
||||
*/
|
||||
class _Stream
|
||||
{
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns an activity stream";
|
||||
|
||||
/*
|
||||
* Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array(
|
||||
'id' => TypeRegistry::id()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return \IPS\core\Stream
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
if( isset( $args['id'] ) && \intval( $args['id'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$stream = \IPS\core\Stream::load( $args['id'] );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Suitable for guests? */
|
||||
if ( !\IPS\Member::loggedIn()->member_id and !( ( $stream->ownership == 'all' or $stream->ownership == 'custom' ) and $stream->read == 'all' and $stream->follow == 'all' and $stream->date_type != 'last_visit' ) )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_STREAM', 'GQL/0003/1', 403 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Start with a blank stream */
|
||||
$stream = \IPS\core\Stream::allActivityStream();
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Streams query
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 10 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Queries;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams query for GraphQL API
|
||||
*/
|
||||
class _Streams
|
||||
{
|
||||
|
||||
/*
|
||||
* @brief Query description
|
||||
*/
|
||||
public static $description = "Returns a list of user's activity streams";
|
||||
|
||||
/*
|
||||
* @brief Query arguments
|
||||
*/
|
||||
public function args()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query return type
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::stream() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves this query
|
||||
*
|
||||
* @param mixed Value passed into this resolver
|
||||
* @param array Arguments
|
||||
* @param array Context values
|
||||
* @return array
|
||||
*/
|
||||
public function resolve($val, $args, $context)
|
||||
{
|
||||
$streams = array();
|
||||
|
||||
foreach ( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_streams', '`member` IS NULL', 'position ASC' ), 'IPS\core\Stream' ) as $stream )
|
||||
{
|
||||
if( \IPS\Member::loggedIn()->member_id || ( !\IPS\Member::loggedIn()->member_id && ( $stream->ownership == 'all' and $stream->read == 'all' and $stream->follow == 'all' and $stream->date_type != 'last_visit' ) ) )
|
||||
{
|
||||
$streams[ $stream->id ] = $stream;
|
||||
}
|
||||
}
|
||||
|
||||
if ( \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
foreach ( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_streams', array( '`member`=?', \IPS\Member::loggedIn()->member_id ), 'position ASC' ), 'IPS\core\Stream' ) as $stream )
|
||||
{
|
||||
$streams[ $stream->id ] = $stream;
|
||||
}
|
||||
}
|
||||
|
||||
return $streams;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Core controller
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core controller for GraphQL API
|
||||
* @todo maybe this shouldn't be a class since it only has a static method?
|
||||
*/
|
||||
abstract class _Query
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the supported query types in this app
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function queries()
|
||||
{
|
||||
return [
|
||||
'activeUsers' => new \IPS\core\api\GraphQL\Queries\ActiveUsers(),
|
||||
'club' => new \IPS\core\api\GraphQL\Queries\Club(),
|
||||
'content' => new \IPS\core\api\GraphQL\Queries\Content(),
|
||||
'group' => new \IPS\core\api\GraphQL\Queries\Group(),
|
||||
'language' => new \IPS\core\api\GraphQL\Queries\Language(),
|
||||
'loginHandlers' => new \IPS\core\api\GraphQL\Queries\LoginHandlers(),
|
||||
'me' => new \IPS\core\api\GraphQL\Queries\Me(),
|
||||
'member' => new \IPS\core\api\GraphQL\Queries\Member(),
|
||||
'messengerConversation' => new \IPS\core\api\GraphQL\Queries\MessengerConversation(),
|
||||
'messengerConversations' => new \IPS\core\api\GraphQL\Queries\MessengerConversations(),
|
||||
'messengerFolders' => new \IPS\core\api\GraphQL\Queries\MessengerFolders(),
|
||||
'mobileMenu' => new \IPS\core\api\GraphQL\Queries\MobileMenu(),
|
||||
'notificationTypes' => new \IPS\core\api\GraphQL\Queries\NotificationTypes(),
|
||||
'ourPicks' => new \IPS\core\api\GraphQL\Queries\OurPicks(),
|
||||
'popularContributors' => new \IPS\core\api\GraphQL\Queries\PopularContributors(),
|
||||
'search' => new \IPS\core\api\GraphQL\Queries\Search(),
|
||||
'settings' => new \IPS\core\api\GraphQL\Queries\Settings(),
|
||||
'stream' => new \IPS\core\api\GraphQL\Queries\Stream(),
|
||||
'streams' => new \IPS\core\api\GraphQL\Queries\Streams(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Core type registry
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\Types;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core type registry. GraphQL requires exactly one instance of each type,
|
||||
* so we'll generate singletons here.
|
||||
* @todo automate this somehow?
|
||||
*/
|
||||
class _TypeRegistry
|
||||
{
|
||||
protected static $activeUsers;
|
||||
protected static $activeUser;
|
||||
protected static $attachment;
|
||||
protected static $attachmentPermissions;
|
||||
protected static $clubNode;
|
||||
protected static $club;
|
||||
protected static $contentReaction;
|
||||
protected static $contentSearchResult;
|
||||
protected static $group;
|
||||
protected static $ignoreOption;
|
||||
protected static $member;
|
||||
protected static $mobileMenu;
|
||||
protected static $language;
|
||||
protected static $login;
|
||||
protected static $messengerConversation;
|
||||
protected static $messengerFolder;
|
||||
protected static $messengerParticipant;
|
||||
protected static $messengerReply;
|
||||
protected static $notification;
|
||||
protected static $notificationMethod;
|
||||
protected static $notificationType;
|
||||
protected static $poll;
|
||||
protected static $pollQuestion;
|
||||
protected static $popularContributor;
|
||||
protected static $profileFieldGroup;
|
||||
protected static $profileField;
|
||||
protected static $promotedItem;
|
||||
protected static $report;
|
||||
protected static $reportReason;
|
||||
protected static $search;
|
||||
protected static $searchResult;
|
||||
protected static $stream;
|
||||
protected static $tag;
|
||||
protected static $settings;
|
||||
protected static $uploadProgress;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Defined to suppress static warnings
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ActiveUsersType
|
||||
*/
|
||||
public static function activeUsers()
|
||||
{
|
||||
return self::$activeUsers ?: (self::$activeUsers = new \IPS\core\api\GraphQL\Types\ActiveUsersType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ActiveUsersType
|
||||
*/
|
||||
public static function activeUser()
|
||||
{
|
||||
return self::$activeUser ?: (self::$activeUser = new \IPS\core\api\GraphQL\Types\ActiveUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AttachmentType
|
||||
*/
|
||||
public static function attachment()
|
||||
{
|
||||
return self::$attachment ?: (self::$attachment = new \IPS\core\api\GraphQL\Types\AttachmentType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AttachmentPermissionsType
|
||||
*/
|
||||
public static function attachmentPermissions()
|
||||
{
|
||||
return self::$attachmentPermissions ?: (self::$attachmentPermissions = new \IPS\core\api\GraphQL\Types\AttachmentPermissionsType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClubNodeType
|
||||
*/
|
||||
public static function clubNode()
|
||||
{
|
||||
return self::$clubNode ?: (self::$clubNode = new \IPS\core\api\GraphQL\Types\ClubNodeType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClubNodeType
|
||||
*/
|
||||
public static function club()
|
||||
{
|
||||
return self::$club ?: (self::$club = new \IPS\core\api\GraphQL\Types\ClubType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContentReactionType
|
||||
*/
|
||||
public static function contentReaction()
|
||||
{
|
||||
return self::$contentReaction ?: (self::$contentReaction = new \IPS\core\api\GraphQL\Types\ContentReactionType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GroupType
|
||||
*/
|
||||
public static function group()
|
||||
{
|
||||
return self::$group ?: (self::$group = new \IPS\core\api\GraphQL\Types\GroupType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IgnoreOptionType
|
||||
*/
|
||||
public static function ignoreOption()
|
||||
{
|
||||
return self::$ignoreOption ?: (self::$ignoreOption = new \IPS\core\api\GraphQL\Types\IgnoreOptionType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MemberType
|
||||
*/
|
||||
public static function member()
|
||||
{
|
||||
return self::$member ?: (self::$member = new \IPS\core\api\GraphQL\Types\MemberType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MobileMenuType
|
||||
*/
|
||||
public static function mobileMenu()
|
||||
{
|
||||
return self::$mobileMenu ?: (self::$mobileMenu = new \IPS\core\api\GraphQL\Types\MobileMenuType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageType
|
||||
*/
|
||||
public static function language()
|
||||
{
|
||||
return self::$language ?: (self::$language = new \IPS\core\api\GraphQL\Types\LanguageType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LoginType
|
||||
*/
|
||||
public static function login()
|
||||
{
|
||||
return self::$login ?: (self::$login = new \IPS\core\api\GraphQL\Types\LoginType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessengerConversationType
|
||||
*/
|
||||
public static function messengerConversation()
|
||||
{
|
||||
return self::$messengerConversation ?: (self::$messengerConversation = new \IPS\core\api\GraphQL\Types\MessengerConversationType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessengerFolderType
|
||||
*/
|
||||
public static function messengerFolder()
|
||||
{
|
||||
return self::$messengerFolder ?: (self::$messengerFolder = new \IPS\core\api\GraphQL\Types\MessengerFolderType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessengerParticipantType
|
||||
*/
|
||||
public static function messengerParticipant()
|
||||
{
|
||||
return self::$messengerParticipant ?: (self::$messengerParticipant = new \IPS\core\api\GraphQL\Types\MessengerParticipantType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessengerReplyType
|
||||
*/
|
||||
public static function messengerReply()
|
||||
{
|
||||
return self::$messengerReply ?: (self::$messengerReply = new \IPS\core\api\GraphQL\Types\MessengerReplyType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NotificationType
|
||||
*/
|
||||
public static function notification()
|
||||
{
|
||||
return self::$notification ?: (self::$notification = new \IPS\core\api\GraphQL\Types\NotificationType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NotificationMethod
|
||||
*/
|
||||
public static function notificationMethod()
|
||||
{
|
||||
return self::$notificationMethod ?: (self::$notificationMethod = new \IPS\core\api\GraphQL\Types\NotificationMethodType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NotificationTypeType
|
||||
*/
|
||||
public static function notificationType()
|
||||
{
|
||||
return self::$notificationType ?: (self::$notificationType = new \IPS\core\api\GraphQL\Types\NotificationTypeType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PollType
|
||||
*/
|
||||
public static function poll()
|
||||
{
|
||||
return self::$poll ?: (self::$poll = new \IPS\core\api\GraphQL\Types\PollType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PollQuestionType
|
||||
*/
|
||||
public static function pollQuestion()
|
||||
{
|
||||
return self::$pollQuestion ?: (self::$pollQuestion = new \IPS\core\api\GraphQL\Types\PollQuestionType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PopularContributorType
|
||||
*/
|
||||
public static function popularContributor()
|
||||
{
|
||||
return self::$popularContributor ?: (self::$popularContributor = new \IPS\core\api\GraphQL\Types\PopularContributorType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ProfileFieldGroupType
|
||||
*/
|
||||
public static function profileFieldGroup()
|
||||
{
|
||||
return self::$profileFieldGroup ?: (self::$profileFieldGroup = new \IPS\core\api\GraphQL\Types\ProfileFieldGroupType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ProfileFieldType
|
||||
*/
|
||||
public static function profileField()
|
||||
{
|
||||
return self::$profileField ?: (self::$profileField = new \IPS\core\api\GraphQL\Types\ProfileFieldType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PromotedItemType
|
||||
*/
|
||||
public static function promotedItem()
|
||||
{
|
||||
return self::$promotedItem ?: (self::$promotedItem = new \IPS\core\api\GraphQL\Types\PromotedItemType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContentSearchResultType
|
||||
*/
|
||||
public static function contentSearchResult()
|
||||
{
|
||||
return self::$contentSearchResult ?: (self::$contentSearchResult = new \IPS\core\api\GraphQL\Types\ContentSearchResultType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReportType
|
||||
*/
|
||||
public static function report()
|
||||
{
|
||||
return self::$report ?: (self::$report = new \IPS\core\api\GraphQL\Types\ReportType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReportReasonType
|
||||
*/
|
||||
public static function reportReason()
|
||||
{
|
||||
return self::$reportReason ?: (self::$reportReason = new \IPS\core\api\GraphQL\Types\ReportReasonType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResultType
|
||||
*/
|
||||
public static function searchResult()
|
||||
{
|
||||
return self::$searchResult ?: (self::$searchResult = new \IPS\core\api\GraphQL\Types\SearchResultType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchType
|
||||
*/
|
||||
public static function search()
|
||||
{
|
||||
return self::$search ?: (self::$search = new \IPS\core\api\GraphQL\Types\SearchType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StreamType
|
||||
*/
|
||||
public static function stream()
|
||||
{
|
||||
return self::$stream ?: (self::$stream = new \IPS\core\api\GraphQL\Types\StreamType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TagType
|
||||
*/
|
||||
public static function tag()
|
||||
{
|
||||
return self::$tag ?: (self::$tag = new \IPS\core\api\GraphQL\Types\TagType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SettingsType
|
||||
*/
|
||||
public static function settings()
|
||||
{
|
||||
return self::$settings ?: (self::$settings = new \IPS\core\api\GraphQL\Types\SettingsType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UploadProgressType
|
||||
*/
|
||||
public static function uploadProgress()
|
||||
{
|
||||
return self::$uploadProgress ?: (self::$uploadProgress = new \IPS\core\api\GraphQL\Types\UploadProgressType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: ActiveUsers Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ActiveUsers for GraphQL API
|
||||
*/
|
||||
class _ActiveUserType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_ActiveUser',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'url' => [
|
||||
'type' => TypeRegistry::url(),
|
||||
'resolve' => function ($user, $args) {
|
||||
return \IPS\Http\Url::createFromString( $user['location_url'] );
|
||||
}
|
||||
],
|
||||
'lang' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'args' => [
|
||||
'action' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'defaultValue' => TRUE
|
||||
]
|
||||
],
|
||||
'resolve' => function ($user, $args) {
|
||||
return self::getLocationLang( $user, $args['action'] );
|
||||
}
|
||||
],
|
||||
'anonymous' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function ($user, $args) {
|
||||
return $user['login_type'] == \IPS\Session\Front::LOGIN_TYPE_ANONYMOUS;
|
||||
}
|
||||
],
|
||||
'ipAddress' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'resolve' => function ($user, $args) {
|
||||
if( \IPS\Member::loggedIn()->modPermission( 'can_use_ip_tools' ) )
|
||||
{
|
||||
return $user['ip_address'];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'timestamp' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'resolve' => function ($user, $args) {
|
||||
return $user['running_time'];
|
||||
}
|
||||
],
|
||||
'user' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'resolve' => function ($user, $args) {
|
||||
if( $user['member_id'] )
|
||||
{
|
||||
return \IPS\Member::load( $user['member_id'] );
|
||||
}
|
||||
|
||||
return new \IPS\Member;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the language string showing what the user is doing
|
||||
* @todo this calls language()->get(), so we need to figure out we can get the keys in advance to load them
|
||||
* @param array $user The active user row from session store
|
||||
* @param boolean $action Use the 'action' version of the language string?
|
||||
* @return String
|
||||
*/
|
||||
protected static function getLocationLang($user, $action)
|
||||
{
|
||||
try
|
||||
{
|
||||
if( !$user['location_lang'] )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'app_user_browsing_community', FALSE, array('sprintf' => array( $user['member_name'] ) ) );
|
||||
}
|
||||
|
||||
if ( $user['location_permissions'] === NULL or $user['location_permissions'] === '*' or \IPS\Member::loggedIn()->inGroup( explode( ',', $user['location_permissions'] ), TRUE ) )
|
||||
{
|
||||
$sprintf = array();
|
||||
$data = json_decode( $user['location_data'], TRUE );
|
||||
|
||||
if ( !empty( $data ) )
|
||||
{
|
||||
foreach ( $data as $key => $parse )
|
||||
{
|
||||
$value = htmlspecialchars( $parse ? \IPS\Member::loggedIn()->language()->get( $key ) : $key, ENT_DISALLOWED, 'UTF-8', FALSE );
|
||||
$sprintf[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if( $action && \IPS\Member::loggedIn()->language()->checkKeyExists($user['location_lang'] . '_action') )
|
||||
{
|
||||
// Add the member name to the sprintf array since the action strings use it
|
||||
array_unshift( $sprintf, $user['member_name'] );
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( $user['location_lang'] . '_action', FALSE, array( 'sprintf' => $sprintf ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( $user['location_lang'], FALSE, array( 'sprintf' => $sprintf ) );
|
||||
}
|
||||
|
||||
return sprintf( $lang, $sprintf );
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ){
|
||||
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: ActiveUsers Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ActiveUsers for GraphQL API
|
||||
*/
|
||||
class _ActiveUsersType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_ActiveUsers',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'count' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'args' => [
|
||||
'includeGuests' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'defaultValue' => FALSE
|
||||
]
|
||||
],
|
||||
'resolve' => function ($val, $args) {
|
||||
$flags = \IPS\Session\Store::ONLINE_MEMBERS;
|
||||
|
||||
if( $args['includeGuests'] )
|
||||
{
|
||||
$flags = \IPS\Session\Store::ONLINE_MEMBERS | \IPS\Session\Store::ONLINE_GUESTS;
|
||||
}
|
||||
|
||||
return \IPS\Session\Store::i()->getOnlineUsers( $flags | \IPS\Session\Store::ONLINE_COUNT_ONLY, 'asc', NULL, NULL, \IPS\Member::loggedIn()->isAdmin() );
|
||||
}
|
||||
],
|
||||
'users' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::activeUser() ),
|
||||
'args' => [
|
||||
'includeGuests' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'defaultValue' => FALSE
|
||||
],
|
||||
'sortDir' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'activeusers_sort_dir',
|
||||
'values' => ['asc', 'desc']
|
||||
]),
|
||||
'defaultValue' => 'desc'
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
],
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0
|
||||
]
|
||||
],
|
||||
'resolve' => function ($val, $args) {
|
||||
$flags = \IPS\Session\Store::ONLINE_MEMBERS;
|
||||
|
||||
if( $args['includeGuests'] )
|
||||
{
|
||||
$flags = \IPS\Session\Store::ONLINE_MEMBERS | \IPS\Session\Store::ONLINE_GUESTS;
|
||||
}
|
||||
$offset = max( $args['offset'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
$users = \IPS\Session\Store::i()->getOnlineUsers( $flags, $args['sortDir'], array( $offset, $limit ), NULL, \IPS\Member::loggedIn()->isAdmin() );
|
||||
|
||||
return $users;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Attachment Permissions Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 28 May 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AttachmentPermissionsType for GraphQL API
|
||||
*/
|
||||
class _AttachmentPermissionsType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_AttachmentPermissions',
|
||||
'description' => 'Details about what the user can attach',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'consumedUploadSize' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "The maximum *combined* size (in bytes) remaining for attachments, or NULL for no restriction. Note: if 0 this means no more uploads are allowed. If the user deletes an attachment, you should query again for an updated value.",
|
||||
'resolve' => function( $data ) {
|
||||
$currentPostUsage = 0;
|
||||
// @todo Currently this only considers postKey because the GraphQL doesn't support editing. When edit support is added we'll need to modify this query to do "OR ( location_key=? AND id=? AND id2=?... )"
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_attachments', array( 'attach_post_key=?', $data['postKey'] ) ) as $attachment )
|
||||
{
|
||||
$currentPostUsage += $attachment['attach_filesize'];
|
||||
}
|
||||
return $currentPostUsage;
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Attachment Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 22 May 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AttachmentType for GraphQL API
|
||||
*/
|
||||
class _AttachmentType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Attachment',
|
||||
'description' => 'An attachment',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "The attachment ID",
|
||||
'resolve' => function( $attachment ) {
|
||||
return $attachment['attach_id'];
|
||||
}
|
||||
],
|
||||
'ext' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "The attachment's filename extension",
|
||||
'resolve' => function( $attachment ) {
|
||||
return $attachment['attach_ext'];
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "The attachment's original filename (not necessarily what is actually stored on disk)",
|
||||
'resolve' => function( $attachment ) {
|
||||
return $attachment['attach_file'];
|
||||
}
|
||||
],
|
||||
'size' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Size of attachment in bytes",
|
||||
'resolve' => function( $attachment ) {
|
||||
return $attachment['attach_filesize'];
|
||||
}
|
||||
],
|
||||
'image' => [
|
||||
'type' => TypeRegistry::image(),
|
||||
'description' => "Image details (will be NULL for non-image files)",
|
||||
'resolve' => function( $attachment ) {
|
||||
if ( $attachment['attach_is_image'] )
|
||||
{
|
||||
return array(
|
||||
'url' => (string) \IPS\File::get( 'core_Attachment', $attachment['attach_location'] )->url,
|
||||
'width' => $attachment['attach_img_width'],
|
||||
'height' => $attachment['attach_img_height'],
|
||||
);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'thumbnail' => [
|
||||
'type' => TypeRegistry::image(),
|
||||
'description' => "A thumbnail for the attachment (will be NULL for non-image files)",
|
||||
'resolve' => function( $attachment ) {
|
||||
if ( $attachment['attach_is_image'] )
|
||||
{
|
||||
if ( $attachment['attach_thumb_location'] )
|
||||
{
|
||||
return array(
|
||||
'url' => (string) \IPS\File::get( 'core_Attachment', $attachment['attach_thumb_location'] )->url,
|
||||
'width' => $attachment['attach_thumb_width'],
|
||||
'height' => $attachment['attach_thumb_height'],
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return array(
|
||||
'url' => (string) \IPS\File::get( 'core_Attachment', $attachment['attach_location'] )->url,
|
||||
'width' => $attachment['attach_img_width'],
|
||||
'height' => $attachment['attach_img_height'],
|
||||
);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'date' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Timestamp of when the attachment was uploaded",
|
||||
'resolve' => function( $attachment ) {
|
||||
return $attachment['attach_date'];
|
||||
}
|
||||
],
|
||||
'uploader' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'description' => "Member who uploaded the attachment",
|
||||
'resolve' => function ( $attachment ) {
|
||||
return \IPS\Member::load( $attachment['attach_member_id'] );
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Club Node Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClubNodeType for GraphQL API
|
||||
*/
|
||||
class _ClubNodeType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_ClubNode',
|
||||
'description' => 'Club Nodes',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Node ID",
|
||||
'resolve' => function ($node, $args, $context, $info) {
|
||||
return $node['node_id'];
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Node name",
|
||||
'resolve' => function ($node, $args, $context, $info) {
|
||||
return $node['name'];
|
||||
}
|
||||
],
|
||||
'seoTitle' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Node's SEO title",
|
||||
'resolve' => function ($node, $args, $context, $info) {
|
||||
return \IPS\Http\Url\Friendly::seoTitle( $node['name'] );
|
||||
}
|
||||
],
|
||||
'type' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Node type (from classname)",
|
||||
'resolve' => function ($node, $args, $context, $info) {
|
||||
return str_replace('\\', '_', str_replace('IPS\\', '', $node['node_class'] ) );
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Club Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClubType for GraphQL API
|
||||
*/
|
||||
class _ClubType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Club',
|
||||
'description' => 'Clubs',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Club ID",
|
||||
'resolve' => function ($club) {
|
||||
return $club->id;
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Club name",
|
||||
'resolve' => function ($club) {
|
||||
return $club->name;
|
||||
}
|
||||
],
|
||||
'type' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Club type",
|
||||
'resolve' => function ($club) {
|
||||
return $club->type;
|
||||
}
|
||||
],
|
||||
'seoTitle' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Club SEO title",
|
||||
'resolve' => function ($club) {
|
||||
return \IPS\Http\Url\Friendly::seoTitle( $club->name );
|
||||
}
|
||||
],
|
||||
'createdDate' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Timestamp of club creation date",
|
||||
'resolve' => function ($club) {
|
||||
return $club->created;
|
||||
}
|
||||
],
|
||||
'memberCount' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Number of members in this club",
|
||||
'resolve' => function ($club) {
|
||||
return $club->members;
|
||||
}
|
||||
],
|
||||
'members' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::member() ),
|
||||
'description' => "List of members in this club",
|
||||
'args' => [
|
||||
'type' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'club_member_type',
|
||||
'values' => ['all', 'leader', 'moderator', 'member']
|
||||
]),
|
||||
'defaultValue' => 'all'
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
]
|
||||
],
|
||||
'resolve' => function ($club, $args) {
|
||||
return self::members( $club, $args );
|
||||
}
|
||||
],
|
||||
'owner' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'description' => "Owner of this club",
|
||||
'resolve' => function ($club) {
|
||||
return $club->owner;
|
||||
}
|
||||
],
|
||||
'icon' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Club icon image",
|
||||
'resolve' => function ($club) {
|
||||
return ( $club->profile_photo ) ? \IPS\File::get( 'core_Clubs', $club->profile_photo )->url : null;
|
||||
}
|
||||
],
|
||||
'coverPhoto' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Club cover photo image",
|
||||
'resolve' => function ($club) {
|
||||
return ( $club->coverPhoto(FALSE)->file ) ? $club->coverPhoto(FALSE)->file : null;
|
||||
}
|
||||
],
|
||||
'isFeatured' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Club featured flag",
|
||||
'resolve' => function ($club) {
|
||||
return $club->featured;
|
||||
}
|
||||
],
|
||||
'about' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Club description",
|
||||
'resolve' => function ($club) {
|
||||
return $club->about;
|
||||
}
|
||||
],
|
||||
'lastActivity' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Timestamp of the last activity in this club",
|
||||
'resolve' => function ($club) {
|
||||
return $club->last_activity;
|
||||
}
|
||||
],
|
||||
'nodes' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::clubNode() ),
|
||||
'description' => "List of nodes in this club",
|
||||
'resolve' => function ($club, $args) {
|
||||
return self::nodes( $club, $args );
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve nodes field
|
||||
*
|
||||
* @param \IPS\Member\Club
|
||||
* @param array Arguments passed to this resolver
|
||||
* @return array
|
||||
*/
|
||||
protected static function nodes($club, $args)
|
||||
{
|
||||
return $club->nodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve members field
|
||||
*
|
||||
* @param \IPS\Member\Club
|
||||
* @param array Arguments passed to this resolver
|
||||
* @return array
|
||||
*/
|
||||
protected static function members($club, $args)
|
||||
{
|
||||
$memberType = ( $args['type'] == 'all' ) ? array( 'member', 'moderator', 'leader' ) : array( $args['type'] );
|
||||
$result = array();
|
||||
$limit = min( $args['limit'], 50 );
|
||||
$members = $club->members( $memberType, $limit, 'core_clubs_memberships.joined ASC', 2 );
|
||||
|
||||
foreach( $members as $memberRow ){
|
||||
$result[] = \IPS\Member::constructFromData( $memberRow );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Content Reactions Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ContentReactionType for GraphQL API
|
||||
*/
|
||||
class _ContentReactionType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Reaction',
|
||||
'description' => 'Content reactions',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Reaction key. If this is a reaction given to content, this will be a unique key. If this is representing a particular reaction as a whole, it will be that reaction's ID.",
|
||||
'resolve' => function ($reaction) {
|
||||
return isset( $reaction['reaction'] ) ? $reaction['reaction']->id : $reaction['id'];
|
||||
}
|
||||
],
|
||||
'reactionId' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "The reaction's actual ID",
|
||||
'resolve' => function ($reaction) {
|
||||
return isset( $reaction['reaction'] ) ? $reaction['reaction']->id : $reaction['reactionId'];
|
||||
}
|
||||
],
|
||||
'count' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Count of this reaction type",
|
||||
'resolve' => function ($reaction) {
|
||||
return isset( $reaction['count'] ) ? $reaction['count'] : 0;
|
||||
}
|
||||
],
|
||||
'image' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Reaction image",
|
||||
'resolve' => function ($reaction) {
|
||||
$_reaction = isset( $reaction['reaction'] ) ? $reaction['reaction'] : \IPS\Content\Reaction::load( $reaction['reactionId'] );
|
||||
return (string) $_reaction->_icon->url;
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Reaction name",
|
||||
'resolve' => function ($reaction) {
|
||||
$_reaction = isset( $reaction['reaction'] ) ? $reaction['reaction'] : \IPS\Content\Reaction::load( $reaction['reactionId'] );
|
||||
return \IPS\Member::loggedIn()->language()->addToStack("reaction_title_{$_reaction->id}", FALSE, array('escape' => TRUE));
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Search result Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ContentSearchResultType for GraphQL API
|
||||
*/
|
||||
class _ContentSearchResultType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_ContentSearchResult',
|
||||
'description' => 'A search result item',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'indexID' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Index ID of this result",
|
||||
'resolve' => function ($result) {
|
||||
return self::getFieldValue('index_id', $result);
|
||||
}
|
||||
],
|
||||
'itemID' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "ID of this result item",
|
||||
'resolve' => function ($result) {
|
||||
return self::getFieldValue('index_item_id', $result);
|
||||
}
|
||||
],
|
||||
'objectID' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "ID of the indexed result",
|
||||
'resolve' => function ($result) {
|
||||
return self::getFieldValue('index_object_id', $result);
|
||||
}
|
||||
],
|
||||
'containerID' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "ID of this result container",
|
||||
'resolve' => function ($result) {
|
||||
return self::getFieldValue('index_container_id', $result);
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::url(),
|
||||
'description' => "URL to this result item",
|
||||
'resolve' => function ($result) {
|
||||
return self::url($result);
|
||||
}
|
||||
],
|
||||
'containerTitle' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Title of this result container",
|
||||
'resolve' => function ($result) {
|
||||
return self::containerTitle( $result );
|
||||
}
|
||||
],
|
||||
'class' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Class of this result item",
|
||||
'resolve' => function ($result) {
|
||||
return self::getFieldValue('index_class', $result);
|
||||
}
|
||||
],
|
||||
'itemClass' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Class of the item type, if this result is a comment/review",
|
||||
'resolve' => function ($result) {
|
||||
$asArray = $result->asArray();
|
||||
$itemClass = static::getItemClass( $asArray );
|
||||
|
||||
return $itemClass;
|
||||
}
|
||||
],
|
||||
// @todo this should really be in a more generic ClassType that returns
|
||||
// data about each class, with appropriate permission checking in place
|
||||
'firstCommentRequired' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Does the result class require a first comment (e.g. topics)?",
|
||||
'resolve' => function ($result) {
|
||||
$asArray = $result->asArray();
|
||||
$itemClass = static::getItemClass( $asArray );
|
||||
|
||||
return $itemClass::$firstCommentRequired;
|
||||
}
|
||||
],
|
||||
'content' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Search result content",
|
||||
'args' => [
|
||||
'truncateLength' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => 'Characters to truncate on.',
|
||||
'defaultValue' => 0
|
||||
]
|
||||
],
|
||||
'resolve' => function ($result, $args) {
|
||||
$string = html_entity_decode( self::getFieldValue('index_content', $result), ENT_QUOTES, 'UTF-8' );
|
||||
$string = preg_replace( "/\r|\n|\t/", '', $string );
|
||||
|
||||
/* Truncate */
|
||||
if( $args['truncateLength'] )
|
||||
{
|
||||
$string = mb_substr( $string, 0, $args['truncateLength'] );
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
],
|
||||
'contentImages' => [
|
||||
'type' => TypeRegistry::listOf( TypeRegistry::string() ),
|
||||
'description' => "Item images for this search result",
|
||||
'resolve' => function ($result) {
|
||||
return self::contentImages( $result );
|
||||
}
|
||||
],
|
||||
'articleLang' => [
|
||||
'type' => new ObjectType([
|
||||
'name' => 'core_articleLang',
|
||||
'fields' => [
|
||||
'indefinite' => TypeRegistry::string(),
|
||||
'definite' => TypeRegistry::string(),
|
||||
'definiteUC' => TypeRegistry::string()
|
||||
],
|
||||
'resolveField' => function ($result, $args, $context, $info) {
|
||||
$asArray = $result->asArray();
|
||||
$classToUse = static::getItemClass( $asArray );
|
||||
|
||||
switch( $info->fieldName )
|
||||
{
|
||||
case 'indefinite':
|
||||
return $classToUse::_indefiniteArticle( $asArray['containerData'] );
|
||||
break;
|
||||
case 'definite':
|
||||
return $classToUse::_definiteArticle( $asArray['containerData'] );
|
||||
break;
|
||||
case 'definiteUC':
|
||||
return $classToUse::_definiteArticle( $asArray['containerData'], NULL, array( 'ucfirst' => TRUE ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
]),
|
||||
'resolve' => function ($result) {
|
||||
return $result;
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Search result item title",
|
||||
'resolve' => function ($result) {
|
||||
return self::title($result);
|
||||
}
|
||||
],
|
||||
'unread' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function ($result) {
|
||||
return self::isUnread( $result );
|
||||
}
|
||||
],
|
||||
'hiddenStatus' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'search_hiddenStatus',
|
||||
'values' => ['HIDDEN', 'PENDING', 'DELETED']
|
||||
]),
|
||||
'resolve' => function ($result) {
|
||||
switch( self::getFieldValue('index_hidden', $result) ){
|
||||
case -2:
|
||||
return 'DELETED';
|
||||
case -1:
|
||||
return 'HIDDEN';
|
||||
case 1:
|
||||
return 'PENDING';
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
],
|
||||
'updated' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Timestamp of when this result was updated",
|
||||
'resolve' => function ($result) {
|
||||
return self::getFieldValue('index_date_updated', $result);
|
||||
}
|
||||
],
|
||||
'created' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Timestamp of when this result was created",
|
||||
'resolve' => function ($result) {
|
||||
return self::getFieldValue('index_date_created', $result);
|
||||
}
|
||||
],
|
||||
'isComment' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this result a comment?",
|
||||
'resolve' => function ($result) {
|
||||
return \in_array( 'IPS\Content\Comment', class_parents( self::getFieldValue('index_class', $result) ) ) && !self::getFieldValue('index_title', $result);
|
||||
}
|
||||
],
|
||||
'isReview' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this result a review?",
|
||||
'resolve' => function ($result) {
|
||||
return \in_array( 'IPS\Content\Review', class_parents( self::getFieldValue('index_class', $result) ) );
|
||||
}
|
||||
],
|
||||
'replies' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Number of replies to the content item",
|
||||
'resolve' => function ($result) {
|
||||
return self::replies($result);
|
||||
}
|
||||
],
|
||||
'relativeTimeKey' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns a string indicating the relative posting time of this item",
|
||||
'resolve' => function ($result) {
|
||||
return $result->streamSeparator();
|
||||
}
|
||||
],
|
||||
'author' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'description' => "Author of this result",
|
||||
'resolve' => function ($result) {
|
||||
return \IPS\Member::load( self::getFieldValue('index_author', $result) );
|
||||
}
|
||||
],
|
||||
'itemAuthor' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'description' => "Author of the original content item",
|
||||
'resolve' => function ($result) {
|
||||
return \IPS\Member::load( self::getFieldValue('index_item_author', $result) );
|
||||
}
|
||||
],
|
||||
'club' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::club(),
|
||||
'description' => "Club this result belongs to, if applicable",
|
||||
'resolve' => function ($result) {
|
||||
return self::club( $result );
|
||||
}
|
||||
],
|
||||
'reactions' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\Api\GraphQL\TypeRegistry::contentReaction() ),
|
||||
'description' => "Reactions for this content",
|
||||
'resolve' => function ($result) {
|
||||
try {
|
||||
$reactions = array();
|
||||
|
||||
if( \count( $result->reactions ) )
|
||||
{
|
||||
foreach( $result->reactions as $reactID => $count )
|
||||
{
|
||||
$reactions[] = array(
|
||||
'id' => $reactID,
|
||||
'reaction' => \IPS\Content\Reaction::load( $reactID ),
|
||||
'count' => $count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $reactions;
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{}
|
||||
|
||||
return array();
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
protected static function getFieldValue( $field, $result )
|
||||
{
|
||||
$asArray = $result->asArray();
|
||||
return $asArray['indexData'][ $field ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of replies
|
||||
*
|
||||
* @param \IPS\Content\Search\Result Search result
|
||||
* @return string
|
||||
*/
|
||||
protected static function replies($result) {
|
||||
$asArray = $result->asArray();
|
||||
$itemData = $asArray['itemData'];
|
||||
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $asArray['indexData']['index_class'] ) ) )
|
||||
{
|
||||
$itemClass = static::getItemClass( $asArray );
|
||||
|
||||
if( isset( $itemClass::$databaseColumnMap['num_comments'] ) and isset( $itemData[ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['num_comments'] ] ) )
|
||||
{
|
||||
if( $itemClass::$firstCommentRequired )
|
||||
{
|
||||
return $itemData[ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['num_comments'] ] - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $itemData[ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['num_comments'] ];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$indexClass = self::getFieldValue('index_class', $result);
|
||||
|
||||
if ( isset( $indexClass::$databaseColumnMap['num_comments'] ) and isset( $itemData[ $indexClass::$databasePrefix . $indexClass::$databaseColumnMap['num_comments'] ] ) )
|
||||
{
|
||||
if ( $indexClass::$firstCommentRequired )
|
||||
{
|
||||
return $itemData[ $indexClass::$databasePrefix . $indexClass::$databaseColumnMap['num_comments'] ] - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $itemData[ $indexClass::$databasePrefix . $indexClass::$databaseColumnMap['num_comments'] ];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content item title
|
||||
*
|
||||
* @param \IPS\Content\Search\Result Search result
|
||||
* @return string
|
||||
*/
|
||||
protected static function title( $result )
|
||||
{
|
||||
$asArray = $result->asArray();
|
||||
$itemClass = static::getItemClass( $asArray );
|
||||
|
||||
// If the content is used as the title (e.g. status updates), don't return anything
|
||||
if( $itemClass::$databaseColumnMap['title'] == 'content' )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $asArray['itemData'][ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['title'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the item URL
|
||||
*
|
||||
* @param \IPS\Content\Search\Result Search result
|
||||
* @return string
|
||||
*/
|
||||
protected static function url( $result )
|
||||
{
|
||||
$asArray = $result->asArray();
|
||||
$itemClass = static::getItemClass( $asArray );
|
||||
$itemUrl = $itemClass::urlFromIndexData( $asArray['indexData'], $asArray['itemData'] );
|
||||
|
||||
/* Object URL */
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $asArray['indexData']['index_class'] ) ) )
|
||||
{
|
||||
if ( \in_array( 'IPS\Content\Review', class_parents( $asArray['indexData']['index_class'] ) ) )
|
||||
{
|
||||
$itemUrl = $itemUrl->setQueryString( array( 'do' => 'findReview', 'review' => $asArray['indexData']['index_object_id'] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$itemUrl = $itemUrl->setQueryString( array( 'do' => 'findComment', 'comment' => $asArray['indexData']['index_object_id'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $itemUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unread status of this content
|
||||
*
|
||||
* @param \IPS\Content\Search\Result Search result
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function isUnread( $result )
|
||||
{
|
||||
$asArray = $result->asArray();
|
||||
$itemClass = static::getItemClass( $asArray );
|
||||
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $itemClass ) ) )
|
||||
{
|
||||
$contentClass = $itemClass::$contentItemClass;
|
||||
$unread = $contentClass::unreadFromData( NULL, $asArray['indexData']['index_date_updated'], $asArray['indexData']['index_date_created'], $asArray['indexData']['index_item_id'], $asArray['indexData']['index_container_id'], FALSE );
|
||||
}
|
||||
else
|
||||
{
|
||||
$unread = $itemClass::unreadFromData( NULL, $asArray['indexData']['index_date_updated'], $asArray['indexData']['index_date_created'], $asArray['indexData']['index_item_id'], $asArray['indexData']['index_container_id'], FALSE );
|
||||
}
|
||||
|
||||
return $unread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve contentImage
|
||||
*
|
||||
* @param array Array representation of \IPS\Content\Search\Result
|
||||
* @param array Arguments passed from resolver
|
||||
* @return string
|
||||
*/
|
||||
protected static function contentImages( $result )
|
||||
{
|
||||
try {
|
||||
$itemClass = static::getItemClass( $result->asArray() );
|
||||
$item = $itemClass::load( self::getFieldValue('index_item_id', $result) );
|
||||
$toReturn = array();
|
||||
|
||||
if ( $images = $item->contentImages( 20 ) )
|
||||
{
|
||||
foreach( $images as $image )
|
||||
{
|
||||
foreach( $image as $extension => $file )
|
||||
{
|
||||
$toReturn[] = (string) \IPS\File::get( $extension, $file )->url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $toReturn;
|
||||
} catch (\Exception $e) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve containerTitle
|
||||
*
|
||||
* @param array Array representation of \IPS\Content\Search\Result
|
||||
* @param array Arguments passed from resolver
|
||||
* @return string
|
||||
*/
|
||||
protected static function containerTitle( $result )
|
||||
{
|
||||
$result = $result->asArray();
|
||||
$itemClass = static::getItemClass( $result );
|
||||
$containerTitle = NULL;
|
||||
|
||||
if ( isset( $itemClass::$containerNodeClass ) )
|
||||
{
|
||||
$containerClass = $itemClass::$containerNodeClass;
|
||||
$containerTitle = $containerClass::titleFromIndexData( $result['indexData'], $result['itemData'], $result['containerData'], FALSE );
|
||||
}
|
||||
|
||||
return $containerTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve club
|
||||
*
|
||||
* @param array Array representation of \IPS\Content\Search\Result
|
||||
* @param array Arguments passed from resolver
|
||||
* @return \IPS\Member\Club
|
||||
*/
|
||||
protected static function club( $result )
|
||||
{
|
||||
if( self::getFieldValue('index_club_id', $result) )
|
||||
{
|
||||
return \IPS\Member\Club::load( self::getFieldValue('index_club_id', $result) );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item class for the result
|
||||
*
|
||||
* @param array Array representation of \IPS\Content\Search\Result
|
||||
* @return string
|
||||
*/
|
||||
protected static function getItemClass( $result )
|
||||
{
|
||||
$indexClass = $result['indexData']['index_class'];
|
||||
$itemClass = ( \in_array( 'IPS\Content\Comment', class_parents( $indexClass ) ) ) ? $indexClass::$itemClass : $indexClass;
|
||||
|
||||
return $itemClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Group Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* GroupType for GraphQL API
|
||||
*/
|
||||
class _GroupType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Group',
|
||||
'description' => 'Member groups',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Group ID",
|
||||
'resolve' => function ($group, $args, $context, $info) {
|
||||
return $group->g_id;
|
||||
}
|
||||
],
|
||||
'groupType' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'groupType',
|
||||
'values' => ['GUEST', 'MEMBER', 'ADMIN']
|
||||
]),
|
||||
'description' => "Is this a guest, member or admin group?",
|
||||
'resolve' => function ($group, $args, $context, $info) {
|
||||
if( $group->g_id == \IPS\Settings::i()->guest_group )
|
||||
{
|
||||
return 'GUEST';
|
||||
}
|
||||
elseif( isset( \IPS\Member::administrators()['g'][ $group->g_id ] ) )
|
||||
{
|
||||
return 'ADMIN';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'MEMBER';
|
||||
}
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Group name",
|
||||
'args' => [
|
||||
'formatted' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'defaultValue' => FALSE
|
||||
]
|
||||
],
|
||||
'resolve' => function ($group, $args, $context, $info) {
|
||||
return ( $args['formatted'] ) ? $group->get_formattedName() : $group->name;
|
||||
}
|
||||
],
|
||||
'canAccessSite' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Can users in this group access the site?",
|
||||
'resolve' => function ($group, $args, $context, $info) {
|
||||
return $group->g_view_board;
|
||||
}
|
||||
],
|
||||
'canAccessOffline' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Can users in this group access the site when it's offline?",
|
||||
'resolve' => function ($group, $args, $context, $info) {
|
||||
return $group->g_access_offline;
|
||||
}
|
||||
],
|
||||
'canTag' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Can users in this group tag content (if enabled)?",
|
||||
'resolve' => function ($group) {
|
||||
return !$group->g_id || !\IPS\Settings::i()->tags_enabled ? FALSE : !( $group->g_bitoptions['gbw_disable_tagging'] );
|
||||
}
|
||||
],
|
||||
'maxMessengerRecipients' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Maximum number of recipients to a PM sent by a member in this group",
|
||||
'resolve' => function ($group) {
|
||||
return \IPS\Member::loggedIn()->group['g_max_mass_pm'];
|
||||
}
|
||||
],
|
||||
'members' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::member() ),
|
||||
'description' => "List of members in this group",
|
||||
'args' => [
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
]
|
||||
],
|
||||
'resolve' => function ($group, $args, $context, $info) {
|
||||
/* If we don't allow filtering by this group, don't return the members in it */
|
||||
if( $group->g_bitoptions['gbw_hide_group'] )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$offset = max( $args['offset'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
return new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array('member_group_id=?', $group->g_id), NULL, array( $offset, $limit ) ), 'IPS\Member' );
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Ignore option Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 29 Oct 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* IgnoreOptionType for GraphQL API
|
||||
*/
|
||||
class _IgnoreOptionType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_IgnoreOption',
|
||||
'description' => 'IgnoreOption type',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Ignore option ID",
|
||||
'resolve' => function ($ignore) {
|
||||
return md5( $ignore['member_id'] . $ignore['type'] );
|
||||
}
|
||||
],
|
||||
'type' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Ignore type name",
|
||||
'resolve' => function ($ignore) {
|
||||
return $ignore['type'];
|
||||
}
|
||||
],
|
||||
'isBeingIgnored' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this member being ignored for the current content type?",
|
||||
'resolve' => function ($ignore) {
|
||||
return $ignore['is_being_ignored'];
|
||||
}
|
||||
],
|
||||
'lang' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "The language string for this ignore type",
|
||||
'resolve' => function ($ignore) {
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'ignore_' . $ignore['type'] );
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Language pack Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* LanguageType for GraphQL API
|
||||
*/
|
||||
class _LanguageType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Language',
|
||||
'description' => 'Language package',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Language name",
|
||||
'resolve' => function ($lang) {
|
||||
return $lang->title;
|
||||
}
|
||||
],
|
||||
'isDefault' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this language pack the site default?",
|
||||
'resolve' => function ($lang) {
|
||||
return (boolean) $lang->default;
|
||||
}
|
||||
],
|
||||
'locale' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the locale key for this language",
|
||||
'resolve' => function ($lang) {
|
||||
if ( preg_match( '/^\w{2}[-_]\w{2}($|\.)/i', $lang->short ) )
|
||||
{
|
||||
return \strtolower( \substr( $lang->short, 0, 2 ) ) . '-' . \strtoupper( \substr( $lang->short, 3, 2 ) );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
],
|
||||
'phrases' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Accepts a JSON object, which should be an array of phrase keys to return. Returned values are key/value pairs representing phrase key and translation.",
|
||||
'args' => [
|
||||
'keys' => [
|
||||
'type' => TypeRegistry::nonNull( TypeRegistry::listOf( TypeRegistry::string() ) )
|
||||
]
|
||||
],
|
||||
'resolve' => function ($lang, $args) {
|
||||
$phrases = $args['keys'];
|
||||
$toReturn = array();
|
||||
|
||||
foreach( $phrases as $phrase )
|
||||
{
|
||||
$toReturn[ $phrase ] = $lang->addToStack( 'app_' . $phrase, FALSE, array('json' => TRUE) );
|
||||
}
|
||||
|
||||
return json_encode($toReturn);
|
||||
}
|
||||
],
|
||||
'phrase' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Get a phrase from this language pack",
|
||||
'args' => [
|
||||
'key' => [
|
||||
'type' => TypeRegistry::nonNull( TypeRegistry::string() )
|
||||
]
|
||||
],
|
||||
'resolve' => function ($lang, $args, $context, $info) {
|
||||
try
|
||||
{
|
||||
return $lang->addToStack( $args['key'] );
|
||||
}
|
||||
catch (\Exception $err)
|
||||
{
|
||||
return $args['key'];
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Login Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 29 Oct 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* LoginType for GraphQL API
|
||||
*/
|
||||
class _LoginType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Login',
|
||||
'description' => 'Login type',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Handler ID",
|
||||
'resolve' => function ($login) {
|
||||
return $login->_id;
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Login name",
|
||||
'resolve' => function ($login) {
|
||||
return \IPS\Member::loggedIn()->language()->get( $login->getTitle() );
|
||||
}
|
||||
],
|
||||
'text' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Login button text",
|
||||
'resolve' => function ($login) {
|
||||
return \IPS\Member::loggedIn()->language()->get( $login->buttonText() );
|
||||
}
|
||||
],
|
||||
'icon' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "URL to the icon for this service",
|
||||
'resolve' => function ($login) {
|
||||
return $login->buttonIcon();
|
||||
}
|
||||
],
|
||||
'color' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Color of this handler's button",
|
||||
'resolve' => function ($login) {
|
||||
return $login->buttonColor();
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "URL to visit to authorize with this handler",
|
||||
'resolve' => function ($login) {
|
||||
$loginClass = new \IPS\Login;
|
||||
return (string) \IPS\Http\Url::internal( $loginClass->url )->setQueryString('csrfKey', \IPS\Session::i()->csrfKey)->setQueryString('_processLogin', $login->_id);
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,689 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Member Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MemberrType for GraphQL API
|
||||
*/
|
||||
class _MemberType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Member',
|
||||
'description' => 'Community members',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Returns the member's ID",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return $member->member_id;
|
||||
}
|
||||
],
|
||||
'email' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the member's email address (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isAuthorized($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return $member->email;
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the URL to member's profile",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return (string) $member->url();
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the member's username",
|
||||
'args' => [
|
||||
'formatted' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'defaultValue' => FALSE
|
||||
]
|
||||
],
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return ( $args['formatted'] ) ? \IPS\Member\Group::load( $member->member_group_id )->formatName( $member->name ) : $member->name;
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the member's title",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return $member->member_title;
|
||||
}
|
||||
],
|
||||
'timezone' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the member's timezone (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isAuthorized($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return $member->timezone;
|
||||
}
|
||||
],
|
||||
'joined' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the date the member joined",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return $member->joined->getTimestamp();
|
||||
}
|
||||
],
|
||||
'notifications' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::notification() ),
|
||||
'description' => "Returns the member's notifications (depending on permissions)",
|
||||
'args' => [
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0,
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
],
|
||||
'sortBy' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'notification_sort',
|
||||
'values' => ['updated_time', 'sent_time', 'read_time', 'unread']
|
||||
]),
|
||||
'defaultValue' => 'updated_time'
|
||||
],
|
||||
'sortDir' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'notification_sort_dir',
|
||||
'values' => ['asc', 'desc']
|
||||
]),
|
||||
'defaultValue' => 'desc'
|
||||
],
|
||||
'unread' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'defaultValue' => NULL
|
||||
]
|
||||
],
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isOwnerMember($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return self::notifications($member, $args, $context);
|
||||
}
|
||||
],
|
||||
'notificationCount' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the member's notification count (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isOwnerMember($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return $member->notification_cnt;
|
||||
}
|
||||
],
|
||||
'posts' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the member's post count",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return $member->member_posts;
|
||||
}
|
||||
],
|
||||
'contentCount' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the member's post count",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return $member->member_posts;
|
||||
}
|
||||
],
|
||||
'reputationCount' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the member's reputation count",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return ( \IPS\Settings::i()->reputation_enabled ) ? $member->pp_reputation_points : null;
|
||||
}
|
||||
],
|
||||
'solvedCount' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the number of items this member has solved",
|
||||
'resolve' => function ($member) {
|
||||
if( !\IPS\Application::appIsEnabled('forums') || !\IPS\forums\Topic::anyContainerAllowsSolvable() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*) as count', 'core_solved_index', array( 'member_id=?', $member->member_id ) )->first();
|
||||
return $count;
|
||||
}
|
||||
],
|
||||
'ip_address' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the member's IP address (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isAuthorized($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return $member->ip_address;
|
||||
}
|
||||
],
|
||||
'warn_level' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the member's warning level (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isAuthorized($member) ){
|
||||
return NULL;
|
||||
}
|
||||
return $member->warn_level;
|
||||
}
|
||||
],
|
||||
'profileViews' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns the number of profile views for this member (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isAuthorized($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return $member->members_profile_views;
|
||||
}
|
||||
],
|
||||
'validating' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Returns the member's validating status (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isAuthorized($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return (bool) $member->members_bitoptions['validating'];
|
||||
}
|
||||
],
|
||||
'group' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::group(),
|
||||
'description' => "Returns the member's primary group",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return \IPS\Member\Group::load( $member->member_group_id );
|
||||
}
|
||||
],
|
||||
'isOnline' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Indicates whether the user is online, taking permissions into account",
|
||||
'resolve' => function ($member) {
|
||||
return ( $member->isOnline() AND !$member->isOnlineAnonymously() ) OR ( $member->isOnlineAnonymously() AND \IPS\Member::loggedIn()->isAdmin() );
|
||||
}
|
||||
],
|
||||
'lastActivity' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the timestamp of the member's last activity",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return \IPS\DateTime::ts( $member->last_activity )->rfc3339();
|
||||
}
|
||||
],
|
||||
'lastVisit' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the timestamp of the member's last visit",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return \IPS\DateTime::ts( $member->last_visit )->rfc3339();
|
||||
}
|
||||
],
|
||||
'lastPost' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the timestamp of member's last post",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return \IPS\DateTime::ts( $this->member_last_post )->rfc3339();
|
||||
}
|
||||
],
|
||||
'secondaryGroups' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::group() ),
|
||||
'description' => "Returns the member's secondary groups (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
if( !self::isAuthorized($member) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return self::secondaryGroups( $member, $args, $context, $info );
|
||||
}
|
||||
],
|
||||
'defaultStream' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "The ID of the user's default stream",
|
||||
'resolve' => function ($member) {
|
||||
if( !self::isAuthorized($member) || $member->defaultStream == FALSE )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $member->defaultStream;
|
||||
}
|
||||
],
|
||||
'allowFollow' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether this member allows others to follow them",
|
||||
'resolve' => function ($member) {
|
||||
return !$member->members_bitoptions['pp_setting_moderate_followers'];
|
||||
}
|
||||
],
|
||||
'follow' => [
|
||||
'type' => TypeRegistry::follow(),
|
||||
'description' => "Returns fields to handle followers/following",
|
||||
'resolve' => function ($member) {
|
||||
return array(
|
||||
'app' => 'core',
|
||||
'area' => 'member',
|
||||
'id' => $member->member_id,
|
||||
'member' => $member
|
||||
);
|
||||
}
|
||||
],
|
||||
'content' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::searchResult() ),
|
||||
'args' => [
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
],
|
||||
],
|
||||
'description' => "Returns the member's content",
|
||||
'resolve' => function ($member, $args) {
|
||||
return self::content($member, $args);
|
||||
}
|
||||
],
|
||||
'clubs' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::club() ),
|
||||
'description' => "Returns the member's clubs (depending on permissions)",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return \IPS\Member\Club::clubs( $member, 25, 'last_activity' );
|
||||
}
|
||||
],
|
||||
'photo' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the member's photo",
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return self::photo($member);
|
||||
}
|
||||
],
|
||||
'coverPhoto' => \IPS\Api\GraphQL\Fields\CoverPhotoField::getDefinition('core_MemberCoverPhoto'),
|
||||
'customFieldGroups' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::profileFieldGroup() ),
|
||||
'resolve' => function( $member, $args, $context, $info ) {
|
||||
return self::customFieldGroups( $member, $args, $context );
|
||||
}
|
||||
],
|
||||
'maxUploadSize' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "The maximum upload size allowed by this user, either globally or per-item (whichever is smaller). NULL indicates no limit.",
|
||||
'resolve' => function( $member ) {
|
||||
if( $member->member_id && !self::isOwnerMember( $member ) )
|
||||
{
|
||||
throw new \IPS\Api\GraphQL\SafeException( 'INVALID_USER', '2S401/1', 403 );
|
||||
}
|
||||
|
||||
return \IPS\Helpers\Form\Editor::maxTotalAttachmentSize( $member, 0 );
|
||||
}
|
||||
],
|
||||
'canBeIgnored' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Can this member be ignored by the current user?",
|
||||
'resolve' => function ($member) {
|
||||
return $member->canBeIgnored();
|
||||
}
|
||||
],
|
||||
'ignoreStatus' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::ignoreOption() ),
|
||||
'description' => "Returns the ignore status of this member, based on the currently-authenticated member's preferences.",
|
||||
'resolve' => function ($member ) {
|
||||
return self::ignoreStatus($member);
|
||||
}
|
||||
],
|
||||
|
||||
// Messenger fields
|
||||
'messengerDisabled' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this member's messenger disabled?",
|
||||
'resolve' => function( $member ) {
|
||||
return (bool) $member->members_disable_pm;
|
||||
}
|
||||
],
|
||||
'messengerNewCount' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Number of new messages",
|
||||
'resolve' => function ($member) {
|
||||
if( $member->member_id && !self::isOwnerMember( $member ) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $member->msg_count_new;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a member's photo
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @return string
|
||||
*/
|
||||
protected static function photo($member)
|
||||
{
|
||||
$member_properties = array();
|
||||
|
||||
foreach( array( 'name', 'pp_main_photo', 'pp_photo_type', 'pp_thumb_photo', 'member_id' ) as $column )
|
||||
{
|
||||
$member_properties[ $column ] = $member->$column;
|
||||
}
|
||||
|
||||
$photoUrl = \IPS\Member::photoUrl( $member_properties );
|
||||
|
||||
if( mb_strpos( $photoUrl, "data:image/svg+xml" ) === 0 )
|
||||
{
|
||||
return static::getLetterPhotoData( $member );
|
||||
}
|
||||
else
|
||||
{
|
||||
return $photoUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a json array containing letter/color combo for a user's letter photo
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @return string
|
||||
*/
|
||||
protected static function getLetterPhotoData($member)
|
||||
{
|
||||
return json_encode( \IPS\Member::generateLetterPhoto( array(
|
||||
'name' => $member->name,
|
||||
'pp_main_photo' => $member->pp_main_photo,
|
||||
'member_id' => $member->member_id
|
||||
), TRUE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this is a user authorized to access sensitive data
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function isAuthorized($member)
|
||||
{
|
||||
return self::isOwnerMember($member) || \IPS\Member::loggedIn()->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this user is the same as the user requesting the info
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function isOwnerMember($member)
|
||||
{
|
||||
return $member->member_id && $member->member_id == \IPS\Member::loggedIn()->member_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a member's content
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function content($member, $args)
|
||||
{
|
||||
// Get page
|
||||
// We don't know the count at this stage, so figure out the page number from
|
||||
// our offset/limit
|
||||
$page = 1;
|
||||
$offset = max( $args['offset'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
|
||||
if( $offset > 0 )
|
||||
{
|
||||
$page = floor( $offset / $limit ) + 1;
|
||||
}
|
||||
|
||||
$latestActivity = \IPS\Content\Search\Query::init()->filterForProfile( $member )->setLimit( $limit )->setPage( $page )->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_CREATED )->search();
|
||||
$latestActivity->init( TRUE );
|
||||
|
||||
return $latestActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return user's notifications
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @param array $args
|
||||
* @param array $context
|
||||
* @return array
|
||||
*/
|
||||
protected static function notifications($member, $args, $context)
|
||||
{
|
||||
/* Specify filter in where clause */
|
||||
$where = array();
|
||||
$where[] = array( "notification_app IN('" . implode( "','", array_keys( \IPS\Application::enabledApplications() ) ) . "')" );
|
||||
$where[] = array( "member=?", \IPS\Member::loggedIn()->member_id );
|
||||
|
||||
/* Are we filtering by unread status? */
|
||||
if( isset( $args['unread'] ) && $args['unread'] !== NULL )
|
||||
{
|
||||
if( $args['unread'] === TRUE )
|
||||
{
|
||||
$where[] = array( "read_time IS NULL" );
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = array( "read_time IS NOT NULL" );
|
||||
}
|
||||
}
|
||||
|
||||
/* Sorting */
|
||||
$sort = $args['sortBy'] . ' ' . $args['sortDir'];
|
||||
|
||||
if( $args['sortBy'] == 'unread' )
|
||||
{
|
||||
$sort = $args['sortDir'] == 'desc' ? 'read_time IS NOT NULL, sent_time DESC' : 'read_time IS NULL, sent_time ASC';
|
||||
}
|
||||
|
||||
/* Get Count */
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*) as cnt', 'core_notifications', $where )->first();
|
||||
|
||||
/* Get results */
|
||||
$returnRows = array();
|
||||
$offset = max( $args['offset'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_notifications', $where, $sort, array( $offset, $limit ) ) as $row )
|
||||
{
|
||||
try
|
||||
{
|
||||
$notification = \IPS\Notification\Api::constructFromData( $row );
|
||||
$returnRows[] = array( 'notification' => $notification, 'data' => $notification->getData( FALSE ) );
|
||||
}
|
||||
catch ( \LogicException $e ) { }
|
||||
}
|
||||
|
||||
return $returnRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return custom profile field groups
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function customFieldGroups($member, $args, $context)
|
||||
{
|
||||
/* Get profile field values */
|
||||
try
|
||||
{
|
||||
$profileFieldValues = \IPS\Db::i()->select( '*', 'core_pfields_content', array( 'member_id=?', $member->member_id ) )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if( !empty( $profileFieldValues ) )
|
||||
{
|
||||
$fields = array();
|
||||
|
||||
if( \IPS\Member::loggedIn()->isAdmin() OR \IPS\Member::loggedIn()->modPermissions() )
|
||||
{
|
||||
$where = array( "pfd.pf_member_hide='owner' OR pfd.pf_member_hide='staff' OR pfd.pf_member_hide='all'" );
|
||||
}
|
||||
elseif( \IPS\Member::loggedIn()->member_id == $member->member_id )
|
||||
{
|
||||
$where = array( "pfd.pf_member_hide='owner' OR pfd.pf_member_hide='all'" );
|
||||
}
|
||||
else
|
||||
{
|
||||
$where = array( "pfd.pf_member_hide='all'" );
|
||||
}
|
||||
|
||||
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( 'pfd.*', array('core_pfields_data', 'pfd'), $where, 'pfg.pf_group_order, pfd.pf_position' )->join(
|
||||
array('core_pfields_groups', 'pfg'),
|
||||
"pfd.pf_group_id=pfg.pf_group_id"
|
||||
), 'IPS\core\ProfileFields\Field' ) as $field )
|
||||
{
|
||||
if( $profileFieldValues[ 'field_' . $field->id ] !== '' AND $profileFieldValues[ 'field_' . $field->id ] !== NULL )
|
||||
{
|
||||
if( !isset( $fields[ $field->group_id ] ) ){
|
||||
$fields[ $field->group_id ] = array(
|
||||
'id' => md5( $member->member_id . $field->group_id ),
|
||||
'groupId' => $field->group_id,
|
||||
'title' => 'core_pfieldgroups_' . $field->group_id,
|
||||
'fields' => array()
|
||||
);
|
||||
}
|
||||
|
||||
$fields[ $field->group_id ]['fields'][] = array(
|
||||
'id' => md5( $member->member_id . $field->id ),
|
||||
'fieldId' => $field->id,
|
||||
'title' => 'core_pfield_' . $field->id,
|
||||
'value' => json_encode( $field->apiValue( $profileFieldValues['field_' . $field->id] ) ),
|
||||
'type' => $field->type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve followers field
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @param array Arguments passed to this resolver
|
||||
* @return array
|
||||
*/
|
||||
protected static function followers($member, $args, $context, $info)
|
||||
{
|
||||
$limit = min( $args['limit'], 50 );
|
||||
|
||||
return array_map(
|
||||
function ($followRow)
|
||||
{
|
||||
return \IPS\Member::load( $followRow['follow_member_id'] );
|
||||
},
|
||||
iterator_to_array( $member->followers( 3, array( 'immediate', 'daily', 'weekly' ), NULL, array(0, $limit ) ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve secondary groups field
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @param array Arguments passed to this resolver
|
||||
* @return array
|
||||
*/
|
||||
protected static function secondaryGroups($member, $args, $context, $info)
|
||||
{
|
||||
$secondaryGroups = array();
|
||||
foreach ( array_filter( array_map( "intval", explode( ',', $member->mgroup_others ) ) ) as $secondaryGroupId )
|
||||
{
|
||||
try
|
||||
{
|
||||
$secondaryGroups[] = \IPS\Member\Group::load( $secondaryGroupId );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
|
||||
return $secondaryGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve ignore status field
|
||||
*
|
||||
* @param \IPS\Member
|
||||
* @return array
|
||||
*/
|
||||
protected static function ignoreStatus($member)
|
||||
{
|
||||
if( !$member->canBeIgnored() || $member->member_id === \IPS\Member::loggedIn()->member_id ){
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$ignore = FALSE;
|
||||
|
||||
try
|
||||
{
|
||||
$ignore = \IPS\core\Ignore::load( $member->member_id, 'ignore_ignore_id', array( 'ignore_owner_id=?', \IPS\Member::loggedIn()->member_id ) );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) {
|
||||
// Just keep ignore as false
|
||||
}
|
||||
|
||||
$ignoreStatus = array();
|
||||
|
||||
foreach ( \IPS\core\Ignore::types() as $type )
|
||||
{
|
||||
$ignoreStatus[] = array(
|
||||
'type' => $type,
|
||||
'is_being_ignored' => !$ignore ? $ignore : $ignore->$type
|
||||
);
|
||||
}
|
||||
|
||||
return $ignoreStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Messenger conversation Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 25 Sep 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MessengerConversation for GraphQL API
|
||||
*/
|
||||
class _MessengerConversationType extends \IPS\Content\Api\GraphQL\ItemType
|
||||
{
|
||||
/*
|
||||
* @brief The item classname we use for this type
|
||||
*/
|
||||
protected static $itemClass = '\IPS\core\Messenger\Conversation';
|
||||
|
||||
/*
|
||||
* @brief GraphQL type name
|
||||
*/
|
||||
protected static $typeName = 'core_MessengerConversation';
|
||||
|
||||
/*
|
||||
* @brief GraphQL type description
|
||||
*/
|
||||
protected static $typeDescription = 'A messenger conversation';
|
||||
|
||||
/**
|
||||
* Return the fields available in this type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fields()
|
||||
{
|
||||
$defaultFields = parent::fields();
|
||||
$conversationFields = array(
|
||||
'folder' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::messengerFolder(),
|
||||
'description' => "Returns the folder ID that this conversation exists in for the logged-in user",
|
||||
'resolve' => function ($message) {
|
||||
$folderObj = \IPS\core\api\GraphQL\TypeRegistry::messengerFolder();
|
||||
$folders = $folderObj->getMemberFolders();
|
||||
|
||||
if( isset( $folders[ $message->map['map_folder_id'] ] ) )
|
||||
{
|
||||
return $folders[ $message->map['map_folder_id'] ];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'participants' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::messengerParticipant() ),
|
||||
'description' => "Returns a list of participants in this conversation",
|
||||
'resolve' => function ($message) {
|
||||
return $message->maps();
|
||||
}
|
||||
],
|
||||
'activeParticipants' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Returns a count of active participants in this conversation",
|
||||
'resolve' => function ($message) {
|
||||
return $message->activeParticipants;
|
||||
}
|
||||
],
|
||||
'participantBlurb' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "A short blurb containing the names of the participants",
|
||||
'resolve' => function ($message) {
|
||||
return $message->participantBlurb();
|
||||
}
|
||||
],
|
||||
'lastMessage' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::messengerReply(),
|
||||
'description' => "The most recent reply to the conversation",
|
||||
'resolve' => function ($message) {
|
||||
return $message->comments( 1, 0, 'date', 'desc' );
|
||||
}
|
||||
],
|
||||
'updated' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Timestamp of when this conversation was last updated",
|
||||
'resolve' => function ($message) {
|
||||
return $message->last_post_time;
|
||||
}
|
||||
],
|
||||
'isUnread' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this conversation unread?",
|
||||
'resolve' => function ($message) {
|
||||
return (bool) $message->map['map_has_unread'];
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// Remove duplicated fields
|
||||
unset( $defaultFields['container'] );
|
||||
unset( $defaultFields['views'] );
|
||||
unset( $defaultFields['isLocked'] );
|
||||
unset( $defaultFields['isPinned'] );
|
||||
unset( $defaultFields['isFeatured'] );
|
||||
unset( $defaultFields['hiddenStatus'] );
|
||||
unset( $defaultFields['updated'] );
|
||||
unset( $defaultFields['follow'] );
|
||||
unset( $defaultFields['tags'] );
|
||||
unset( $defaultFields['hasPoll'] );
|
||||
unset( $defaultFields['poll'] );
|
||||
|
||||
|
||||
return array_merge( $defaultFields, $conversationFields );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return item permission fields.
|
||||
* Here we adjust the resolver for the commentInformation field to check whether this is
|
||||
* a poll-only topic.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getItemPermissionFields()
|
||||
{
|
||||
$defaultFields = parent::getItemPermissionFields();
|
||||
|
||||
// Messenger does not restrict based on locked status etc.
|
||||
$defaultFields['commentInformation']['resolve'] = function () {
|
||||
return NULL;
|
||||
};
|
||||
|
||||
return $defaultFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the available sorting options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOrderByOptions()
|
||||
{
|
||||
// Note: these options all get prefixed with 'mt_' when passed into the query.
|
||||
return array('last_post_time', 'start_time', 'replies');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment type that goes with this item type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
protected static function getCommentType()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::messengerReply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the comments field - overridden from ItemType
|
||||
* If this is a question and order isn't date, then force order by votes
|
||||
*
|
||||
* @param \IPS\forums\Topic
|
||||
* @param array Arguments passed to this resolver
|
||||
* @return array
|
||||
*/
|
||||
/*protected static function comments($topic, $args)
|
||||
{
|
||||
return parent::comments($topic, $args);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Messenger folder Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 27 Sep 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MessengerFolder for GraphQL API
|
||||
*/
|
||||
class _MessengerFolderType extends ObjectType
|
||||
{
|
||||
|
||||
public $memberFolders = NULL;
|
||||
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_MessengerFolder',
|
||||
'description' => 'Messenger folder',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Folder ID",
|
||||
'resolve' => function ($folder) {
|
||||
return $folder['id'];
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "The name of this folder (as provided by the user)",
|
||||
'resolve' => function ($folder) {
|
||||
return $folder['name'];
|
||||
}
|
||||
],
|
||||
'count' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Number of conversations in this folder",
|
||||
'resolve' => function ($folder) {
|
||||
return $folder['count'];
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::url(),
|
||||
'description' => "URL to this folder",
|
||||
'resolve' => function ($folder) {
|
||||
return $folder['url'];
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of messenger folder data for the logged-in member
|
||||
*
|
||||
* @Param boolean $refetch If true, will force folder data to be refetched rather than served from cached values
|
||||
* @return array
|
||||
*/
|
||||
public function getMemberFolders($refetch = FALSE)
|
||||
{
|
||||
if( $this->memberFolders !== NULL && !$refetch )
|
||||
{
|
||||
return $this->memberFolders;
|
||||
}
|
||||
|
||||
/* Get folders */
|
||||
$folders = array( 'myconvo' => \IPS\Member::loggedIn()->language()->addToStack('messenger_folder_inbox') );
|
||||
if ( \IPS\Member::loggedIn()->pconversation_filters )
|
||||
{
|
||||
$folders = $folders + array_filter( json_decode( \IPS\Member::loggedIn()->pconversation_filters, TRUE ) );
|
||||
}
|
||||
|
||||
/* What are our folder counts? */
|
||||
/* Note: The setKeyField and setValueField calls here were causing the folder counts to be incorrect (if you had two folders with 1 message in each, then both showed a count of 2) */
|
||||
$counts = iterator_to_array( \IPS\Db::i()->select( 'map_folder_id, count(*) as count', 'core_message_topic_user_map', array( 'map_user_id=? AND map_user_active=1', \IPS\Member::loggedIn()->member_id ), NULL, NULL, 'map_folder_id' ) );
|
||||
$folderCounts = array();
|
||||
foreach( $counts AS $k => $count )
|
||||
{
|
||||
$folderCounts[$count['map_folder_id']] = $count['count'];
|
||||
}
|
||||
|
||||
foreach( $folders as $id => $name)
|
||||
{
|
||||
$this->memberFolders[ $id ] = array(
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
'url' => \IPS\Http\Url::internal( "app=core&module=messaging&controller=messenger", 'front', 'messaging' )->setQueryString('folder', $id),
|
||||
'count' => isset( $folderCounts[ $id ] ) ? $folderCounts[ $id ] : 0
|
||||
);
|
||||
}
|
||||
|
||||
return $this->memberFolders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Messenger participant Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 27 Sep 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MessengerParticipant for GraphQL API
|
||||
*/
|
||||
class _MessengerParticipantType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_MessengerParticipant',
|
||||
'description' => 'Messenger participant',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Participant ID (this is not the member ID, but an ID relative to the conversation)",
|
||||
'resolve' => function ($map) {
|
||||
return $map['map_id'];
|
||||
}
|
||||
],
|
||||
'member' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'description' => "The member",
|
||||
'resolve' => function ($map) {
|
||||
if( $map['map_user_id'] )
|
||||
{
|
||||
return \IPS\Member::load( $map['map_user_id'] );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'isActive' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => 'Is the member active in this conversation?',
|
||||
'resolve' => function ($map) {
|
||||
return $map['map_user_active'];
|
||||
}
|
||||
],
|
||||
'isBanned' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => 'Has the member been removed from this conversation?',
|
||||
'resolve' => function ($map) {
|
||||
return $map['map_user_banned'];
|
||||
}
|
||||
],
|
||||
'lastRead' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => 'Timestamp of when the member last read this conversation',
|
||||
'resolve' => function ($map) {
|
||||
if( $map['map_read_time'] !== 0 )
|
||||
{
|
||||
return $map['map_read_time'];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'leftDate' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => 'The timestamp of when the participant left the conversation if they are no longer active',
|
||||
'resolve' => function ($map) {
|
||||
if( !$map['map_user_active'] && $map['map_left_time'] )
|
||||
{
|
||||
return $map['map_left_time'];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'lastReply' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => 'The timestamp of when the participant last replied',
|
||||
'resolve' => function ($map) {
|
||||
if( $map['map_last_topic_reply'] )
|
||||
{
|
||||
return $map['map_last_topic_reply'];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Messenger reply Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 25 Sep 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MessengerReply for GraphQL API
|
||||
*/
|
||||
class _MessengerReplyType extends \IPS\Content\Api\GraphQL\CommentType
|
||||
{
|
||||
/*
|
||||
* @brief The item classname we use for this type
|
||||
*/
|
||||
protected static $commentClass = '\IPS\core\Messenger\Message';
|
||||
|
||||
/*
|
||||
* @brief GraphQL type name
|
||||
*/
|
||||
protected static $typeName = 'core_MessengerReply';
|
||||
|
||||
/*
|
||||
* @brief GraphQL type description
|
||||
*/
|
||||
protected static $typeDescription = 'A reply';
|
||||
|
||||
/**
|
||||
* Get the item type that goes with this item type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public static function getItemType()
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::messengerConversation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fields available in this type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
/*public function fields()
|
||||
{
|
||||
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Return the definite article, but without the item type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
/*public static function definiteArticleNoItem($post, $options = array())
|
||||
{
|
||||
$type = $post->item()->isQuestion() ? 'answer_lc' : 'post_lc';
|
||||
return \IPS\Member::loggedIn()->language()->addToStack($type, FALSE, $options);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Mobile Menu Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 12 June 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mobile Menu Type for GraphQL API
|
||||
*/
|
||||
class _MobileMenuType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_MobileMenu',
|
||||
'description' => 'Mobile menu item type',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Item ID",
|
||||
'resolve' => function ( $item ) {
|
||||
return $item->id;
|
||||
}
|
||||
],
|
||||
'class' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Class which handles this item",
|
||||
'resolve' => function ( $item ) {
|
||||
return \get_class( $item );
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Menu item title",
|
||||
'resolve' => function ($item) {
|
||||
return $item->title();
|
||||
}
|
||||
],
|
||||
'icon' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Icon key for this item",
|
||||
'resolve' => function ($item) {
|
||||
return $item->icon();
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::url(),
|
||||
'description' => "URL to content",
|
||||
'resolve' => function ( $item ) {
|
||||
return $item->link();
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Notification method type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 30 Jan 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationMethodType for GraphQL API
|
||||
*/
|
||||
class _NotificationMethodType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_NotificationMethod',
|
||||
'description' => 'Notification method',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Method name",
|
||||
'resolve' => function ($method) {
|
||||
return $method['name'];
|
||||
}
|
||||
],
|
||||
'disabled' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this method disabled?",
|
||||
'resolve' => function ($method) {
|
||||
return $method['disabled'];
|
||||
}
|
||||
],
|
||||
'default' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "THe default state for this method",
|
||||
'resolve' => function ($method) {
|
||||
return $method['default'];
|
||||
}
|
||||
],
|
||||
'value' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "The member's setting",
|
||||
'resolve' => function ($method) {
|
||||
return $method['member'];
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: NotificationType group Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationType for GraphQL API
|
||||
*/
|
||||
class _NotificationType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Notification',
|
||||
'description' => 'Notification',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Notification ID",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->id;
|
||||
}
|
||||
],
|
||||
'type' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Notification type",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->notification_key;
|
||||
}
|
||||
],
|
||||
'app' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Notification app",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->app;
|
||||
}
|
||||
],
|
||||
'class' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Notification content class",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->item_class;
|
||||
}
|
||||
],
|
||||
'itemID' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "ID of the content item",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->item_id;
|
||||
}
|
||||
],
|
||||
'sentDate' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Sent timestamp",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->sent;
|
||||
}
|
||||
],
|
||||
'updatedDate' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Updated timestamp",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->updated;
|
||||
}
|
||||
],
|
||||
'readDate' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Read timestamp",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['notification']->read_time;
|
||||
}
|
||||
],
|
||||
'author' => [
|
||||
'type' => \IPS\core\Api\GraphQL\TypeRegistry::member(),
|
||||
'description' => "Member that triggered this notification",
|
||||
'resolve' => function ($notification) {
|
||||
return isset( $notification['data']['author'] ) ? $notification['data']['author'] : NULL;
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Notification title",
|
||||
'resolve' => function ($notification) {
|
||||
return isset( $notification['data']['title'] ) ? $notification['data']['title'] : NULL;
|
||||
}
|
||||
],
|
||||
'content' => [
|
||||
'type' => TypeRegistry::richText(),
|
||||
'description' => "Notification content",
|
||||
'resolve' => function ($notification) {
|
||||
return isset( $notification['data']['content'] ) ? $notification['data']['content'] : NULL;
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::url(),
|
||||
'description' => "URL to content that triggered notification",
|
||||
'resolve' => function ($notification) {
|
||||
return $notification['data']['url'];
|
||||
}
|
||||
],
|
||||
'unread' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this notification unread by the recipient?",
|
||||
'resolve' => function ($notification) {
|
||||
return (bool) $notification['data']['unread'];
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Notification types
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 30 Jan 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationTypeType for GraphQL API
|
||||
*/
|
||||
class _NotificationTypeType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_NotificationType',
|
||||
'description' => 'Notification type',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Type ID",
|
||||
'resolve' => function ($type) {
|
||||
return $type['id'];
|
||||
}
|
||||
],
|
||||
'extension' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Type extension",
|
||||
'resolve' => function ($type) {
|
||||
return $type['extension'];
|
||||
}
|
||||
],
|
||||
'group' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Title of the notification group",
|
||||
'resolve' => function ($type) {
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'notifications__' . $type['extension'] );
|
||||
}
|
||||
],
|
||||
'type' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Type type",
|
||||
'resolve' => function ($type) {
|
||||
return $type['type'];
|
||||
}
|
||||
],
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Type name",
|
||||
'resolve' => function ($type) {
|
||||
return $type['name'];
|
||||
}
|
||||
],
|
||||
'description' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Type description",
|
||||
'resolve' => function ($type) {
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( $type['description'] );
|
||||
}
|
||||
],
|
||||
'lang' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "The translated label",
|
||||
'resolve' => function ($type) {
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( $type['name'] );
|
||||
}
|
||||
],
|
||||
'inline' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::notificationMethod(),
|
||||
'description' => "Inline notification method",
|
||||
'resolve' => function ($type) {
|
||||
return $type['inline'];
|
||||
}
|
||||
],
|
||||
'email' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::notificationMethod(),
|
||||
'description' => "Email notification method",
|
||||
'resolve' => function ($type) {
|
||||
return $type['email'];
|
||||
}
|
||||
],
|
||||
'push' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::notificationMethod(),
|
||||
'description' => "Push notification method",
|
||||
'resolve' => function ($type) {
|
||||
if ( !isset( $type['push'] ) || !\count( $type['push'] ) || !\IPS\Settings::i()->mobile_app_id || !\IPS\Member::loggedIn()->members_bitoptions['mobile_notifications'] )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $type['push'];
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Poll Question Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 30 Nov 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* PollQuestionType for GraphQL API
|
||||
*/
|
||||
class _PollQuestionType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_PollQuestion',
|
||||
'description' => 'A poll question',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Returns the poll question ID",
|
||||
'resolve' => function ($question) {
|
||||
return $question['id'];
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Returns the question title",
|
||||
'resolve' => function ($question) {
|
||||
return $question['data']['question'];
|
||||
}
|
||||
],
|
||||
'isMultiChoice' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this question multiple-choice?",
|
||||
'resolve' => function ($question) {
|
||||
return $question['data']['multi'];
|
||||
}
|
||||
],
|
||||
'votes' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "How many users voted on this question",
|
||||
'resolve' => function ($question) {
|
||||
return isset( $question['data']['votes'] ) ? array_sum( $question['data']['votes'] ) : 0;
|
||||
}
|
||||
],
|
||||
'choices' => [
|
||||
'type' => TypeRegistry::listOf( new ObjectType([
|
||||
'name' => 'core_PollChoice',
|
||||
'description' => "A poll choice",
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'resolve' => function ($choice) {
|
||||
return $choice['id'];
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'resolve' => function ($choice) {
|
||||
return $choice['title'];
|
||||
}
|
||||
],
|
||||
'votes' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'resolve' => function ($choice) {
|
||||
return $choice['votes'];
|
||||
}
|
||||
],
|
||||
'votedFor' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether the logged-in member voted for this choice",
|
||||
'resolve' => function ($choice) {
|
||||
return \in_array( \IPS\Member::loggedIn()->member_id, $choice['voters'] );
|
||||
}
|
||||
],
|
||||
'voters' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::member() ),
|
||||
'args' => [
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
]
|
||||
],
|
||||
'resolve' => function ($choice, $args) {
|
||||
$members = array();
|
||||
$offset = max( $args['offset'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
$membersToLoad = \array_slice( $choice['voters'], $offset, $limit );
|
||||
|
||||
foreach( $membersToLoad as $memberID )
|
||||
{
|
||||
$members[] = \IPS\Member::load( $memberID );
|
||||
}
|
||||
|
||||
return $members;
|
||||
}
|
||||
]
|
||||
]
|
||||
]) ),
|
||||
'description' => "The choices for this question",
|
||||
'resolve' => function ($question) {
|
||||
$choices = array();
|
||||
$canViewCount = \IPS\Member::loggedIn()->modPermission('can_edit_poll_votes');
|
||||
|
||||
foreach( $question['data']['choice'] as $idx => $choice )
|
||||
{
|
||||
$choices[ $idx ] = array(
|
||||
'id' => $question['id'] . '-c' . $idx,
|
||||
'poll' => $question['poll'],
|
||||
'title' => $choice,
|
||||
'votes' => isset( $question['data']['votes'] ) ? $question['data']['votes'][ $idx ] : 0,
|
||||
'voters' => isset( $question['voters'][ $idx ] ) ? $question['voters'][ $idx ] : array()
|
||||
);
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Poll Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 30 Nov 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* PollType for GraphQL API
|
||||
*/
|
||||
class _PollType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Poll',
|
||||
'description' => 'Polls',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Returns the poll ID",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->pid;
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "The poll question",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->poll_question;
|
||||
}
|
||||
],
|
||||
'votes' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Number of votes on this poll",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->votes;
|
||||
}
|
||||
],
|
||||
'questions' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::pollQuestion() ),
|
||||
'description' => "The poll questions",
|
||||
'resolve' => function ($poll) {
|
||||
return static::getQuestions($poll);
|
||||
}
|
||||
],
|
||||
'closeTimestamp' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Timestamp of when this poll will close (NULL if no close date set)",
|
||||
'resolve' => function ($poll) {
|
||||
if( $poll->poll_close_date instanceof \IPS\DateTime )
|
||||
{
|
||||
return $poll->poll_close_date->getTimestamp();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'isClosed' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether this poll is closed to further votes",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->poll_closed;
|
||||
}
|
||||
],
|
||||
'isPublic' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether this is a public poll (i.e. voter names are public)",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->poll_view_voters;
|
||||
}
|
||||
],
|
||||
'hasVoted' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Has the user voted in this poll?",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->getVote() !== NULL;
|
||||
}
|
||||
],
|
||||
'canVote' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether the user can vote in this poll",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->canVote();
|
||||
}
|
||||
],
|
||||
'canViewResults' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether the user can view the results of this poll",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->canViewResults();
|
||||
}
|
||||
],
|
||||
'canViewVoters' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether the user can view who voted, based on permissions",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->canSeeVoters();
|
||||
}
|
||||
],
|
||||
'canClose' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether the user can close this poll",
|
||||
'resolve' => function ($poll) {
|
||||
return $poll->canClose();
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve poll questions field
|
||||
*
|
||||
* @param \IPS\Poll
|
||||
* @return array
|
||||
*/
|
||||
protected static function getQuestions ($poll) {
|
||||
$questions = array();
|
||||
|
||||
foreach ( $poll->choices as $idx => $choice )
|
||||
{
|
||||
// We provide the index as a ID since questions don't have a real ID. Providing an ID
|
||||
// allows GraphQL (well, Apollo) to optimize queries.
|
||||
// We need to pass `poll` through here since there's no way to obtain a reference to the poll
|
||||
// from the individual question/choices
|
||||
$questions[ $idx ] = array(
|
||||
'id' => 'p' . $poll->pid . '-q' . $idx,
|
||||
'data' => $choice,
|
||||
'poll' => $poll,
|
||||
'voters' => array()
|
||||
);
|
||||
}
|
||||
|
||||
if( $poll->canSeeVoters() )
|
||||
{
|
||||
// Get the voters for each choice in advance here, but we won't load the member
|
||||
// until questions -> choices -> voters is resolved in PollQuestionType
|
||||
$voters = array();
|
||||
$query = \IPS\Db::i()->select( '*', 'core_voters', array( 'poll=?', $poll->pid ) );
|
||||
|
||||
foreach( $query as $voter )
|
||||
{
|
||||
if( $voter['member_choices'] !== NULL )
|
||||
{
|
||||
$voteData = json_decode( $voter['member_choices'] );
|
||||
foreach( $voteData as $questionIdx => $answer )
|
||||
{
|
||||
if( \is_array( $answer ) )
|
||||
{
|
||||
foreach( $answer as $_answer )
|
||||
{
|
||||
$questions[ $questionIdx ]['voters'][ \intval( $_answer ) ][] = $voter['member_id'];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$questions[ $questionIdx ]['voters'][ \intval( $answer ) ][] = $voter['member_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $questions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Popular Contributors Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 11 Feb 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* PopularContributorsType for GraphQL API
|
||||
*/
|
||||
class _PopularContributorType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_PopularContributor',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'rep' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'resolve' => function ($contributor, $args) {
|
||||
return $contributor['rep'];
|
||||
}
|
||||
],
|
||||
'user' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'resolve' => function ($contributor, $args) {
|
||||
if( $contributor['member_id'] )
|
||||
{
|
||||
return \IPS\Member::load( $contributor['member_id'] );
|
||||
}
|
||||
|
||||
return new \IPS\Member;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Profile field group Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* StreamType for GraphQL API
|
||||
*/
|
||||
class _ProfileFieldGroupType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_ProfileFieldGroup',
|
||||
'description' => 'Custom profile field groups',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Profile field group ID (relative to member)",
|
||||
'resolve' => function ($fieldGroup) {
|
||||
return $fieldGroup['id'];
|
||||
}
|
||||
],
|
||||
'groupId' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Actual profile field group ID",
|
||||
'resolve' => function ($fieldGroup) {
|
||||
return $fieldGroup['groupId'];
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Profile field group title",
|
||||
'resolve' => function ($fieldGroup) {
|
||||
return \IPS\Member::loggedIn()->language()->get( $fieldGroup['title'] );
|
||||
}
|
||||
],
|
||||
'fields' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::profileField() ),
|
||||
'description' => "List of fields in this profile field group",
|
||||
'resolve' => function ($fieldGroup) {
|
||||
return $fieldGroup['fields'];
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Profile field Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProfileFieldType for GraphQL API
|
||||
*/
|
||||
class _ProfileFieldType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$config = [
|
||||
'name' => 'core_ProfileField',
|
||||
'description' => 'Custom profile field',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Profile field ID, relative to member",
|
||||
'resolve' => function ($field) {
|
||||
return $field['id'];
|
||||
}
|
||||
],
|
||||
'fieldId' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Actual profile field ID",
|
||||
'resolve' => function ($field) {
|
||||
return $field['fieldId'];
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Profile field title",
|
||||
'resolve' => function ($field) {
|
||||
return \IPS\Member::loggedIn()->language()->get( $field['title'] );
|
||||
}
|
||||
],
|
||||
'value' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Profile field value",
|
||||
'resolve' => function ($field) {
|
||||
return $field['value'];
|
||||
}
|
||||
],
|
||||
'type' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Profile field type",
|
||||
'resolve' => function ($field) {
|
||||
return $field['type'];
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: PromotedItemType group Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* PromotedItemType for GraphQL API
|
||||
*/
|
||||
class _PromotedItemType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_PromotedItem',
|
||||
'description' => 'Promoted Item',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => 'Promote ID',
|
||||
'resolve' => function ($item) {
|
||||
return $item->id;
|
||||
}
|
||||
],
|
||||
'addedBy' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'description' => 'Who promoted this item',
|
||||
'resolve' => function ($item) {
|
||||
return \IPS\Member::load( $item->added_by );
|
||||
}
|
||||
],
|
||||
'images' => [
|
||||
'type' => TypeRegistry::listOf( TypeRegistry::string() ),
|
||||
'description' => 'Photos attached to the promoted item',
|
||||
'resolve' => function ($item) {
|
||||
$images = array();
|
||||
|
||||
if( \count( $item->imageObjects() ) )
|
||||
{
|
||||
foreach( $item->imageObjects() as $file )
|
||||
{
|
||||
$images[] = (string) $file->url;
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => 'Promoted title',
|
||||
'resolve' => function ($item) {
|
||||
return $item->ourPicksTitle;
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::url(),
|
||||
'description' => 'URL to original item',
|
||||
'resolve' => function ($item) {
|
||||
return $item->object()->url();
|
||||
}
|
||||
],
|
||||
'item' => [
|
||||
'type' => new UnionType([
|
||||
'name' => 'core_PromotedItem_TypeUnion',
|
||||
'types' => [
|
||||
\IPS\Content\Api\GraphQL\TypeRegistry::comment(),
|
||||
\IPS\Content\Api\GraphQL\TypeRegistry::item(),
|
||||
\IPS\Node\Api\GraphQL\TypeRegistry::node()
|
||||
],
|
||||
'resolveType' => function ($item) {
|
||||
if ( $item instanceof \IPS\Content\Comment )
|
||||
{
|
||||
return \IPS\Content\Api\GraphQL\TypeRegistry::comment();
|
||||
}
|
||||
else if ( $item instanceof \IPS\Content\Item )
|
||||
{
|
||||
return \IPS\Content\Api\GraphQL\TypeRegistry::item();
|
||||
}
|
||||
else if( $item instanceof \IPS\Node\Model )
|
||||
{
|
||||
return \IPS\Node\Api\GraphQL\TypeRegistry::node();
|
||||
}
|
||||
}
|
||||
]),
|
||||
'description' => 'The original item',
|
||||
'resolve' => function ($item) {
|
||||
return $item->object();
|
||||
}
|
||||
],
|
||||
'itemType' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'core_PromotedItem_Type',
|
||||
'values' => ['COMMENT', 'REVIEW', 'ITEM', 'NODE']
|
||||
]),
|
||||
'description' => "What kind of content is this item?",
|
||||
'resolve' => function ($item) {
|
||||
if ( $item->object() instanceof \IPS\Content\Comment )
|
||||
{
|
||||
return 'COMMENT';
|
||||
}
|
||||
else if ( $item->object() instanceof \IPS\Content\Review )
|
||||
{
|
||||
return 'REVIEW';
|
||||
}
|
||||
else if ( $item->object() instanceof \IPS\Content )
|
||||
{
|
||||
return 'ITEM';
|
||||
}
|
||||
else if ( $item->object() instanceof \IPS\Node\Model )
|
||||
{
|
||||
return 'NODE';
|
||||
}
|
||||
|
||||
print_r( $item->object() );
|
||||
exit;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'description' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => 'Promoted blurb provided by staff',
|
||||
'resolve' => function ($item) {
|
||||
$text = trim( $item->getText('internal', false) );
|
||||
|
||||
if( $text ){
|
||||
return $text;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
],
|
||||
'timestamp' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => 'Timestamp for when item was promoted',
|
||||
'resolve' => function ($item) {
|
||||
return $item->sent;
|
||||
}
|
||||
],
|
||||
'reputation' => [
|
||||
'type' => TypeRegistry::reputation(),
|
||||
'resolve' => function ($item) {
|
||||
$reactionClass = $item->objectReactionClass;
|
||||
return $reactionClass;
|
||||
}
|
||||
],
|
||||
'dataCount' => [
|
||||
'type' => new ObjectType([
|
||||
'name' => 'core_ourPicks_dataCount',
|
||||
'fields' => [
|
||||
'count' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'resolve' => function ($array) {
|
||||
return $array['count'];
|
||||
}
|
||||
],
|
||||
'words' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'resolve' => function ($array) {
|
||||
return $array['words'];
|
||||
}
|
||||
]
|
||||
]
|
||||
]),
|
||||
'resolve' => function ($item) {
|
||||
if( $item->objectDataCount ){
|
||||
return $item->objectDataCount;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Report reason type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 8 Jun 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ReportReasonType for GraphQL API
|
||||
*/
|
||||
class _ReportReasonType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_ReportReason',
|
||||
'description' => 'Report reason type',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Reason ID",
|
||||
'resolve' => function ($reason) {
|
||||
return $reason['id'];
|
||||
}
|
||||
],
|
||||
'reason' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Reason description",
|
||||
'resolve' => function ($reason) {
|
||||
return $reason['reason'];
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Report type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 14 Jun 2019
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ReportType for GraphQL API
|
||||
*/
|
||||
class _ReportType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Report',
|
||||
'description' => 'Report type',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Report ID",
|
||||
'resolve' => function ($content) {
|
||||
if( $this->hasReported( $content ) ) {
|
||||
return $content->reportData['id'];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
],
|
||||
'hasReported' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Has the user reported this?",
|
||||
'resolve' => function ($content) {
|
||||
return $this->hasReported($content);
|
||||
}
|
||||
],
|
||||
'reportType' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "If the user has reported, the report type they made",
|
||||
'resolve' => function ($content) {
|
||||
if( $this->hasReported( $content ) ) {
|
||||
return $content->reportData['report_type'];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
],
|
||||
'reportDate' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "If the user has reported, the date of the report",
|
||||
'resolve' => function ($content) {
|
||||
if( $this->hasReported( $content ) ) {
|
||||
return $content->reportData['date_reported'];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
],
|
||||
'reportContent' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "If the user has reported, the report message if any",
|
||||
'resolve' => function ($content) {
|
||||
if( $this->hasReported( $content ) ) {
|
||||
return $content->reportData['report'];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the user has reported this content
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function hasReported($content)
|
||||
{
|
||||
return ( $content->canReport( \IPS\Member::loggedIn() ) === 'report_err_already_reported' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Union type for search results
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 22 Sep 2018
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Search result union type
|
||||
*/
|
||||
class _SearchResultType extends UnionType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_SearchResult',
|
||||
'description' => 'A union type that returns either a member, or a content search result',
|
||||
'types' => [
|
||||
\IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
\IPS\core\api\GraphQL\TypeRegistry::contentSearchResult()
|
||||
],
|
||||
'resolveType' => function ($result) {
|
||||
if ( $result instanceof \IPS\Member )
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::member();
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\core\api\GraphQL\TypeRegistry::contentSearchResult();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Search Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 22 Sep 2018
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchType for GraphQL API
|
||||
*/
|
||||
class _SearchType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Search',
|
||||
'description' => 'Search results',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'count' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "Total number of results",
|
||||
'resolve' => function ($search) {
|
||||
return $search['count'];
|
||||
}
|
||||
],
|
||||
'results' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::searchResult() ),
|
||||
'description' => "List of items in this stream",
|
||||
'resolve' => function ($search, $args, $context) {
|
||||
return $search['results'];
|
||||
}
|
||||
],
|
||||
'types' => [
|
||||
'type' => TypeRegistry::listOf( new ObjectType([
|
||||
'name' => 'core_search_types',
|
||||
'description' => "The available search types",
|
||||
'fields' => [
|
||||
'key' => TypeRegistry::string(),
|
||||
'lang' => TypeRegistry::string()
|
||||
],
|
||||
'resolveField' => function ($type, $args, $context, $info) {
|
||||
switch( $info->fieldName )
|
||||
{
|
||||
case 'key':
|
||||
return $type;
|
||||
break;
|
||||
case 'lang':
|
||||
return \IPS\Member::loggedIn()->language()->get( $type . '_pl' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
]) ),
|
||||
'resolve' => function () {
|
||||
return array_merge( array('core_members'), array_keys( \IPS\core\modules\front\search\search::contentTypes() ) );
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Settings field Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* SettingsType for GraphQL API
|
||||
*/
|
||||
class _SettingsType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$config = [
|
||||
'name' => 'core_Settings',
|
||||
'description' => 'System settings',
|
||||
'fields' => function () {
|
||||
return [
|
||||
// Invidival settings are specified here to ensure that only whitelisted settings
|
||||
// are exposed via the API.
|
||||
|
||||
/* -------------------------------- */
|
||||
/* Basic system settings */
|
||||
'base_url' => [
|
||||
'type' => TypeRegistry::string()
|
||||
],
|
||||
'tags_enabled' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'tags_min' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
],
|
||||
'tags_max' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
],
|
||||
'tags_min_req' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'tags_len_min' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
],
|
||||
'tags_len_max' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
],
|
||||
'tags_open_system' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'site_online' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
],
|
||||
'site_offline_message' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
],
|
||||
'board_name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
],
|
||||
'reputation_enabled' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'reputation_highlight' => [
|
||||
'type' => TypeRegistry::int()
|
||||
],
|
||||
'reputation_show_profile' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'allow_reg' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'allowReg',
|
||||
'values' => ['NORMAL', 'FULL', 'REDIRECT', 'DISABLED']
|
||||
]),
|
||||
'resolve' => function ($val, $args, $context, $info) {
|
||||
return \strtoupper( \IPS\Login::registrationType() );
|
||||
}
|
||||
],
|
||||
'allow_reg_target' => [
|
||||
'type' => TypeRegistry::string()
|
||||
],
|
||||
'disable_anonymous' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'allow_result_view' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'geolocation_enabled' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function () {
|
||||
return \IPS\GeoLocation::enabled();
|
||||
}
|
||||
],
|
||||
'mobileHomeBlocks' => [
|
||||
'type' => TypeRegistry::listOf( TypeRegistry::string() ),
|
||||
'resolve' => function () {
|
||||
try
|
||||
{
|
||||
$parsed = json_decode( \IPS\Settings::i()->mobile_app_home_blocks_order, TRUE );
|
||||
$blocksToShow = array_filter( $parsed );
|
||||
|
||||
return array_keys( $blocksToShow );
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return array("new_content", "active_users", "our_picks", "popular_contributors");
|
||||
}
|
||||
}
|
||||
],
|
||||
'version' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'resolve' => function () {
|
||||
return \IPS\Application::load('core')->long_version;
|
||||
}
|
||||
],
|
||||
|
||||
/* -------------------------------- */
|
||||
/* Legal settings */
|
||||
'privacy_type' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'privacyType',
|
||||
'values' => ['INTERNAL', 'EXTERNAL', 'NONE']
|
||||
]),
|
||||
'resolve' => function ($val) {
|
||||
return mb_strtoupper( \IPS\Settings::i()->privacy_type );
|
||||
}
|
||||
],
|
||||
'privacy_text' => [
|
||||
'type' => TypeRegistry::richText(),
|
||||
'resolve' => function () {
|
||||
return \IPS\Member::loggedIn()->language()->get('privacy_text_value');
|
||||
}
|
||||
],
|
||||
'privacy_link' => [
|
||||
'type' => TypeRegistry::string()
|
||||
],
|
||||
'reg_rules' => [
|
||||
'type' => TypeRegistry::richText(),
|
||||
'resolve' => function () {
|
||||
return \IPS\Member::loggedIn()->language()->get('reg_rules_value');
|
||||
}
|
||||
],
|
||||
'guidelines_type' => [
|
||||
'type' => TypeRegistry::eNum([
|
||||
'name' => 'guidelinesType',
|
||||
'values' => ['INTERNAL', 'EXTERNAL', 'NONE']
|
||||
]),
|
||||
'resolve' => function ($val) {
|
||||
return mb_strtoupper( \IPS\Settings::i()->gl_type );
|
||||
}
|
||||
],
|
||||
'guidelines_text' => [
|
||||
'type' => TypeRegistry::richText(),
|
||||
'resolve' => function ($val) {
|
||||
return \IPS\Member::loggedIn()->language()->get('guidelines_value');
|
||||
}
|
||||
],
|
||||
'guidelines_link' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'resolve' => function ($val) {
|
||||
return \IPS\Settings::i()->gl_link;
|
||||
}
|
||||
],
|
||||
|
||||
/* -------------------------------- */
|
||||
/* Forums settings */
|
||||
'forums_questions_downvote' => [
|
||||
'type' => TypeRegistry::boolean()
|
||||
],
|
||||
'forums_uses_solved' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'resolve' => function ($val) {
|
||||
return \IPS\Application::appIsEnabled('forums') && \IPS\forums\Topic::anyContainerAllowsSolvable();
|
||||
}
|
||||
],
|
||||
|
||||
/* -------------------------------- */
|
||||
/* Upload settings */
|
||||
'allowedFileTypes' => [
|
||||
'type' => TypeRegistry::listOf( TypeRegistry::string() ),
|
||||
'description' => "File extensions that are allowed to be uploaded or NULL for any extensions. Note: may be an empty array which means attachments not allowed.",
|
||||
'resolve' => function() {
|
||||
return \IPS\Helpers\Form\Editor::allowedFileExtensions();
|
||||
}
|
||||
],
|
||||
'chunkingSupported' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "If chunking is supported",
|
||||
'resolve' => function() {
|
||||
$storageClass = \IPS\File::getClass( 'core_Attachment' );
|
||||
return $storageClass::$supportsChunking;
|
||||
}
|
||||
],
|
||||
'maxChunkSize' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'description' => "The maximum size (in bytes) the server can handle without crashing. If chunking is supported, you can send chunks of up to this size - if it isn't, this effectively becomes the maximum size per file.",
|
||||
'resolve' => function() {
|
||||
return \IPS\Helpers\Form\Upload::maxChunkSize();
|
||||
}
|
||||
],
|
||||
|
||||
/* -------------------------------- */
|
||||
/* Report settings */
|
||||
'automoderationEnabled' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Whether the automoderation feature is enabled",
|
||||
'resolve' => function () {
|
||||
return \IPS\Settings::i()->automoderation_enabled;
|
||||
}
|
||||
],
|
||||
'reportReasons' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::reportReason() ),
|
||||
'deescription' => "The available reasons for reporting content",
|
||||
'resolve' => function () {
|
||||
$options = array();
|
||||
|
||||
$options[] = array(
|
||||
'id' => \IPS\core\Reports\Report::TYPE_MESSAGE,
|
||||
'reason' => \IPS\Member::loggedIn()->language()->addToStack('report_message_item')
|
||||
);
|
||||
|
||||
foreach( \IPS\core\Reports\Types::roots() as $type )
|
||||
{
|
||||
$options[] = array(
|
||||
'id' => $type->id,
|
||||
'reason' => $type->_title
|
||||
);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
]
|
||||
];
|
||||
},
|
||||
'resolveField' => function ($val, $args, $context, $info) {
|
||||
$setting = $info->fieldName;
|
||||
try
|
||||
{
|
||||
return \IPS\Settings::i()->$setting;
|
||||
}
|
||||
catch(\Exception $error)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Stream Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* StreamType for GraphQL API
|
||||
*/
|
||||
class _StreamType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Stream',
|
||||
'description' => 'Activity streams',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => TypeRegistry::id(),
|
||||
'description' => "Stream ID",
|
||||
'resolve' => function ($stream) {
|
||||
return $stream->id;
|
||||
}
|
||||
],
|
||||
'title' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Stream title",
|
||||
'resolve' => function ($stream) {
|
||||
if( !$stream->title ) {
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( "stream_title_{$stream->id}" );
|
||||
}
|
||||
return $stream->title;
|
||||
}
|
||||
],
|
||||
'member' => [
|
||||
'type' => \IPS\core\api\GraphQL\TypeRegistry::member(),
|
||||
'description' => "Stream owner, if applicable",
|
||||
'resolve' => function ($stream) {
|
||||
return ( $stream->member ) ? \IPS\Member::load( $stream->member ) : null;
|
||||
}
|
||||
],
|
||||
'isDefault' => [
|
||||
'type' => TypeRegistry::boolean(),
|
||||
'description' => "Is this a default stream?",
|
||||
'resolve' => function ($stream) {
|
||||
return !( $stream->member );
|
||||
}
|
||||
],
|
||||
'items' => [
|
||||
'type' => TypeRegistry::listOf( \IPS\core\api\GraphQL\TypeRegistry::contentSearchResult() ),
|
||||
'description' => "List of items in this stream",
|
||||
'args' => [
|
||||
'offset' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 0
|
||||
],
|
||||
'limit' => [
|
||||
'type' => TypeRegistry::int(),
|
||||
'defaultValue' => 25
|
||||
],
|
||||
'club' => TypeRegistry::int(),
|
||||
'unread' => TypeRegistry::boolean(),
|
||||
|
||||
],
|
||||
'resolve' => function ($stream, $args, $context) {
|
||||
return self::items( $stream, $args, $context );
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve items
|
||||
*
|
||||
* @param \IPS\core\Stream
|
||||
* @param array Arguments passed from resolver
|
||||
* @return array
|
||||
*/
|
||||
protected static function items( $stream, $args, $context )
|
||||
{
|
||||
/* Clubs */
|
||||
if( isset( $args['club'] ) )
|
||||
{
|
||||
$stream->clubs = $args['club'];
|
||||
}
|
||||
|
||||
/* Unread */
|
||||
if( isset( $args['unread'] ) )
|
||||
{
|
||||
if( ( $args['unread'] === TRUE && $stream->read !== 'unread' ) || ( $args['unread'] === FALSE && $stream->read === 'unread' ) )
|
||||
{
|
||||
$stream->read = ( $args['unread'] ) ? 'unread' : 'all';
|
||||
}
|
||||
}
|
||||
|
||||
/* Build the query */
|
||||
$query = $stream->query( \IPS\Member::loggedIn() );
|
||||
|
||||
// Get page
|
||||
// We don't know the count at this stage, so figure out the page number from
|
||||
// our offset/limit
|
||||
$page = 1;
|
||||
$offset = max( $args['offset'], 0 );
|
||||
$limit = min( $args['limit'], 50 );
|
||||
|
||||
if( $offset > 0 )
|
||||
{
|
||||
$page = floor( $offset / $limit ) + 1;
|
||||
}
|
||||
|
||||
$query->setLimit( $limit )->setPage( $page );
|
||||
|
||||
/* Get the results */
|
||||
$results = $query->search( NULL, NULL, ( $stream->include_comments ? \IPS\Content\Search\Query::TAGS_MATCH_ITEMS_ONLY + \IPS\Content\Search\Query::TERM_OR_TAGS : \IPS\Content\Search\Query::TERM_OR_TAGS ) );
|
||||
|
||||
/* Load data we need like the authors, etc */
|
||||
$results->init();
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Tag Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 7 May 2017
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* TagType for GraphQL API
|
||||
*/
|
||||
class _TagType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_Tag',
|
||||
'description' => 'A community tag',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Tag name",
|
||||
'resolve' => function ($tag) {
|
||||
return $tag;
|
||||
}
|
||||
],
|
||||
'url' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Tag URL",
|
||||
'resolve' => function ($tag) {
|
||||
$_tag = urlencode($tag);
|
||||
return \IPS\Http\Url::internal( "app=core&module=search&controller=search&tags={$_tag}", 'front', 'tags' );
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief GraphQL: Upload progress Type
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Community Suite
|
||||
* @since 27 Mar 2020
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\core\api\GraphQL\Types;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use IPS\Api\GraphQL\TypeRegistry;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* UploadProgressType for GraphQL API
|
||||
*/
|
||||
class _UploadProgressType extends ObjectType
|
||||
{
|
||||
/**
|
||||
* Get object type
|
||||
*
|
||||
* @return ObjectType
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = [
|
||||
'name' => 'core_UploadProgress',
|
||||
'description' => 'Upload progress',
|
||||
'fields' => function () {
|
||||
return [
|
||||
'name' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "The attachment's original filename (not necessarily what is actually stored on disk)",
|
||||
'resolve' => function( $attachment ) {
|
||||
return $attachment['name'];
|
||||
}
|
||||
],
|
||||
'ref' => [
|
||||
'type' => TypeRegistry::string(),
|
||||
'description' => "Upload ref",
|
||||
'resolve' => function( $attachment ) {
|
||||
return $attachment['ref'];
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,734 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Clubs API
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 7 June 2018
|
||||
*/
|
||||
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clubs API
|
||||
*/
|
||||
class _clubs extends \IPS\Api\Controller
|
||||
{
|
||||
/**
|
||||
* GET /core/clubs
|
||||
* Get list of clubs
|
||||
*
|
||||
* @apiparam int page Page number
|
||||
* @apiparam int perPage Number of results per page - defaults to 25
|
||||
* @note For requests using an OAuth Access Token for a particular member, only clubs the authorized user can view will be included
|
||||
* @return \IPS\Api\PaginatedResponse<IPS\Member\Club>
|
||||
*/
|
||||
public function GETindex()
|
||||
{
|
||||
$page = isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1;
|
||||
$perPage = isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : 25;
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Api\PaginatedResponse(
|
||||
200,
|
||||
\IPS\Member\Club::clubs( $this->member ?: NULL, array( ( $page - 1 ) * $perPage, $perPage ), 'created' ),
|
||||
$page,
|
||||
'IPS\Member\Club',
|
||||
\IPS\Member\Club::clubs( $this->member ?: NULL, array( ( $page - 1 ) * $perPage, $perPage ), 'created', FALSE, array(), NULL, TRUE ),
|
||||
$this->member,
|
||||
$perPage
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/clubs/{id}
|
||||
* Get specific club
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @throws 1C386/1 INVALID_ID The club does not exist or the authorized user does not have permission to view it
|
||||
* @return \IPS\Member\Club
|
||||
*/
|
||||
public function GETitem( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
if ( $this->member and !$club->canView( $this->member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 200, $club->apiOutput( $this->member ) );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/1', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /core/clubs
|
||||
* Create a club
|
||||
*
|
||||
* @reqapiparam string name The club name
|
||||
* @reqapiparam int owner The club owner (if not using an OAuth Access Token for a particular member)
|
||||
* @apiparam string about Information about the club
|
||||
* @apiparam string type Club type (one of public, open, private, readonly or closed). Defaults to open.
|
||||
* @apiparam bool approved Whether the club is approved or not (if not using an OAuth Access Token for a particular member)
|
||||
* @apiparam bool featured Whether the club is featured or not (if not using an OAuth Access Token for a particular member)
|
||||
* @apiparam float lat Latitude of the club
|
||||
* @apiparam float long Longitude of the club
|
||||
* @apiparam string showMemberTab Who can see the list of members: nonmember = Everyone can see, member = Only members can see, moderator = Only moderators can see. Defaults to nonmember.
|
||||
* @apiparam \IPS\nexus\Money joiningFee Cost to join the club (Nexus must be installed, paid clubs must be enabled, and the owner must be allowed to create paid clubs)
|
||||
* @apiparam \IPS\nexus\Purchase\RenewalTerm renewalTerm Renewal term for the club (joiningFee must be set)
|
||||
* @return \IPS\Member\Club
|
||||
* @note For requests using an OAuth Access Token for a particular member, the authorized user will be the club owner, otherwise you must pass an owner parameter with a valid member ID to set the club owner
|
||||
* @throws 1C386/3 OWNER_REQUIRED An owner for the club is required. For requests NOT using an OAuth Access Token for a particular member, you must supply a member ID for the owner property
|
||||
* @throws 1C386/4 NAME_REQUIRED A name is required for the club
|
||||
* @throws 1C386/J CANNOT_CREATE The authorized member or supplied owner cannot create the type of club requested
|
||||
* @throws 1C386/K CLUB_LIMIT_REACHED The authorized member or supplied owner has reached the maximum number of clubs they are allowed to create based on group restrictions
|
||||
*/
|
||||
public function POSTindex()
|
||||
{
|
||||
/* We need an owner */
|
||||
if( !$this->member AND !\IPS\Request::i()->owner )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'OWNER_REQUIRED', '1C386/3', 400 );
|
||||
}
|
||||
|
||||
$owner = $this->member ?: \IPS\Member::load( \IPS\Request::i()->owner );
|
||||
|
||||
if( !\IPS\Request::i()->name )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NAME_REQUIRED', '1C386/4', 400 );
|
||||
}
|
||||
|
||||
$availableTypes = array();
|
||||
|
||||
foreach ( explode( ',', $owner->group['g_create_clubs'] ) as $type )
|
||||
{
|
||||
if ( $type !== '' )
|
||||
{
|
||||
$availableTypes[ $type ] = 'club_type_' . $type;
|
||||
}
|
||||
}
|
||||
|
||||
/* Default club type to 'open' if not specified */
|
||||
if( !isset( \IPS\Request::i()->type ) OR !\IPS\Request::i()->type )
|
||||
{
|
||||
\IPS\Request::i()->type = 'open';
|
||||
}
|
||||
|
||||
if ( !$availableTypes OR !\in_array( \IPS\Request::i()->type, array_keys( $availableTypes ) ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'CANNOT_CREATE', '1C386/J', 403 );
|
||||
}
|
||||
|
||||
if ( $owner->group['g_club_limit'] )
|
||||
{
|
||||
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_clubs', array( 'owner=?', $owner->member_id ) )->first() >= $owner->group['g_club_limit'] )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'CLUB_LIMIT_REACHED', '1C386/K', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
$club = new \IPS\Member\Club;
|
||||
$club->owner = $owner;
|
||||
|
||||
$this->setClubProperties( $club );
|
||||
|
||||
$club->save();
|
||||
|
||||
$club->addMember( $owner, \IPS\Member\Club::STATUS_LEADER );
|
||||
$club->recountMembers();
|
||||
|
||||
if( \IPS\Settings::i()->clubs_require_approval and !$club->approved )
|
||||
{
|
||||
$club->sendModeratorApprovalNotification( $owner );
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 201, $club->apiOutput( $this->member ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /core/clubs/{id}
|
||||
* Edit a club
|
||||
*
|
||||
* @reqapiparam string name The club name
|
||||
* @apiparam string about Information about the club
|
||||
* @apiparam string type Club type (one of public, open, private, readonly or closed). Defaults to open.
|
||||
* @apiparam bool approved Whether the club is approved or not (if not using an OAuth Access Token for a particular member)
|
||||
* @apiparam bool featured Whether the club is featured or not (if not using an OAuth Access Token for a particular member)
|
||||
* @apiparam float lat Latitude of the club
|
||||
* @apiparam float long Longitude of the club
|
||||
* @apiparam string showMemberTab Who can see the list of members: nonmember = Everyone can see, member = Only members can see, moderator = Only moderators can see
|
||||
* @apiparam \IPS\nexus\Money joiningFee Cost to join the club (Nexus must be installed, paid clubs must be enabled, and the owner must be allowed to create paid clubs)
|
||||
* @apiparam \IPS\nexus\Purchase\RenewalTerm renewalTerm Renewal term for the club (joiningFee must be set)
|
||||
* @param int $id ID Number
|
||||
* @return \IPS\Member\Club
|
||||
* @throws 1C386/5 INVALID_ID The club ID was invalid or the authorized member does not have permission to edit it
|
||||
* @throws 1C386/L CANNOT_CREATE The authorized member or supplied owner cannot create the type of club requested
|
||||
*/
|
||||
public function POSTitem( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
|
||||
if( $this->member AND !$club->isLeader( $this->member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/5', 404 );
|
||||
}
|
||||
|
||||
/* Make sure the club type is allowed */
|
||||
if( isset( \IPS\Request::i()->type ) )
|
||||
{
|
||||
$owner = $this->member ?: $club->owner;
|
||||
|
||||
$availableTypes = array();
|
||||
|
||||
foreach ( explode( ',', $owner->group['g_create_clubs'] ) as $type )
|
||||
{
|
||||
if ( $type !== '' )
|
||||
{
|
||||
$availableTypes[ $type ] = 'club_type_' . $type;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$availableTypes OR !\in_array( \IPS\Request::i()->type, array_keys( $availableTypes ) ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'CANNOT_CREATE', '1C386/L', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
$this->setClubProperties( $club );
|
||||
|
||||
$club->save();
|
||||
|
||||
return new \IPS\Api\Response( 200, $club->apiOutput( $this->member ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set common club properties
|
||||
*
|
||||
* @param \IPS\Member\Club $club The club object to set the properties on
|
||||
* @return void
|
||||
*/
|
||||
protected function setClubProperties( $club )
|
||||
{
|
||||
if( \IPS\Request::i()->name )
|
||||
{
|
||||
$club->name = \IPS\Request::i()->name;
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->about ) )
|
||||
{
|
||||
$club->about = \IPS\Request::i()->about;
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->showMemberTab ) AND \in_array( \IPS\Request::i()->showMemberTab, array( 'member', 'nonmember', 'moderator' ) ) )
|
||||
{
|
||||
$club->show_membertab = \IPS\Request::i()->showMemberTab;
|
||||
}
|
||||
else
|
||||
{
|
||||
$club->show_membertab = 'nonmember';
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->type ) AND \in_array( \IPS\Request::i()->type, array( 'open', 'public', 'readonly', 'closed', 'private' ) ) )
|
||||
{
|
||||
$club->type = \IPS\Request::i()->type;
|
||||
}
|
||||
|
||||
if( !$this->member AND isset( \IPS\Request::i()->approved ) )
|
||||
{
|
||||
$club->approved = \IPS\Request::i()->approved;
|
||||
}
|
||||
/* Set club approval based on AdminCP settings, but only if this is a new club */
|
||||
elseif( !$club->id )
|
||||
{
|
||||
$club->approved = \IPS\Settings::i()->clubs_require_approval ? 0 : 1;
|
||||
}
|
||||
|
||||
if( !$this->member AND isset( \IPS\Request::i()->featured ) )
|
||||
{
|
||||
$club->featured = \IPS\Request::i()->featured;
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->clubs_locations AND isset( \IPS\Request::i()->lat ) AND isset( \IPS\Request::i()->long ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$location = \IPS\GeoLocation::getByLatLong( \IPS\Request::i()->lat, \IPS\Request::i()->long );
|
||||
|
||||
$club->location_json = json_encode( $location );
|
||||
$club->location_lat = $location->lat;
|
||||
$club->location_long = $location->long;
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->joiningFee ) AND \IPS\Request::i()->joiningFee )
|
||||
{
|
||||
if ( $club->owner->member_id AND \IPS\Application::appIsEnabled( 'nexus' ) and \IPS\Settings::i()->clubs_paid_on and $club->owner->group['gbw_paid_clubs'] )
|
||||
{
|
||||
$club->fee = json_encode( \IPS\Request::i()->joiningFee );
|
||||
|
||||
if ( isset( \IPS\Request::i()->renewalTerm ) AND \IPS\Request::i()->renewalTerm )
|
||||
{
|
||||
$club->renewal_term = \IPS\Request::i()->renewalTerm['term'];
|
||||
$club->renewal_units = \IPS\Request::i()->renewalTerm['unit'];
|
||||
$club->renewal_price = json_encode( \IPS\Request::i()->renewalTerm['cost'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$club->renewal_term = 0;
|
||||
$club->renewal_units = NULL;
|
||||
$club->renewal_price = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /core/clubs/{id}
|
||||
* Delete a club
|
||||
*
|
||||
* @apiclientonly
|
||||
* @param int $id ID Number
|
||||
* @return null
|
||||
* @throws 1C386/2 INVALID_ID The club does not exist
|
||||
*/
|
||||
public function DELETEitem( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
|
||||
/* Deletions cannot be performed by a regular user */
|
||||
if( $this->member )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/2', 404 );
|
||||
}
|
||||
|
||||
/* Get nodes and queue for deletion */
|
||||
$nodes = $club->nodes();
|
||||
|
||||
foreach( $nodes as $data )
|
||||
{
|
||||
try
|
||||
{
|
||||
$class = $data['node_class'];
|
||||
$node = $class::load( $data['node_id'] );
|
||||
|
||||
$nodesToQueue = array( $node );
|
||||
$nodeToCheck = $node;
|
||||
while( $nodeToCheck->hasChildren( NULL ) )
|
||||
{
|
||||
foreach ( $nodeToCheck->children( NULL ) as $nodeToCheck )
|
||||
{
|
||||
$nodesToQueue[] = $nodeToCheck;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $nodesToQueue as $_node )
|
||||
{
|
||||
\IPS\Task::queue( 'core', 'DeleteOrMoveContent', array( 'class' => $class, 'id' => $_node->_id, 'deleteWhenDone' => TRUE, 'additional' => array() ) );
|
||||
}
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
|
||||
/* Now delete the club and associated data */
|
||||
$club->delete();
|
||||
\IPS\Db::i()->delete( 'core_clubs_memberships', array( 'club_id=?', $club->id ) );
|
||||
\IPS\Db::i()->delete( 'core_clubs_node_map', array( 'club_id=?', $club->id ) );
|
||||
\IPS\Db::i()->delete( 'core_clubs_fieldvalues', array( 'club_id=?', $club->id ) );
|
||||
|
||||
return new \IPS\Api\Response( 200, NULL );
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/clubs/{id}/members
|
||||
* Get members of a club
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @throws 1C386/6 INVALID_ID The club does not exist or the authorized user does not have permission to view it
|
||||
* @throws 1C386/I NO_MEMBERS_PUBLIC_CLUB The club is a public club which has no member list
|
||||
* @apiresponse \IPS\Member owner Club owner
|
||||
* @apiresponse [\IPS\Member] members Club members
|
||||
* @apiresponse [\IPS\Member] leaders Club leaders
|
||||
* @apiresponse [\IPS\Member] moderators Club moderators
|
||||
* @return array
|
||||
*/
|
||||
public function GETitem_members( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
|
||||
if( $this->member AND !$club->canView( $this->member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
elseif( $club->type === \IPS\Member\Club::TYPE_PUBLIC )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
$members = array();
|
||||
$leaders = array();
|
||||
$moderators = array();
|
||||
|
||||
foreach( $club->members( array( 'member', 'moderator', 'leader' ), 250, 'core_clubs_memberships.joined DESC', 2 ) as $member )
|
||||
{
|
||||
$member = \IPS\Member::constructFromData( $member );
|
||||
|
||||
if( $club->owner != $member )
|
||||
{
|
||||
if( $club->isLeader( $member ) )
|
||||
{
|
||||
$leaders[] = $member->apiOutput();
|
||||
}
|
||||
elseif( $club->isModerator( $member ) )
|
||||
{
|
||||
$moderators[] = $member->apiOutput();
|
||||
}
|
||||
else
|
||||
{
|
||||
$members[] = $member->apiOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 200, array( 'owner' => $club->owner ? $club->owner->apiOutput() : NULL, 'members' => $members, 'leaders' => $leaders, 'moderators' => $moderators ) );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/6', 404 );
|
||||
}
|
||||
catch( \BadMethodCallException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_MEMBERS_PUBLIC_CLUB', '1C386/I', 400 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /core/clubs/{id}/members
|
||||
* Add (or invite) a member to a club
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @apiparam int id Member (ID) to add to the club
|
||||
* @apiparam string status Status of the member being added or updated (member, invited, requested, banned, moderator, leader)
|
||||
* @apiparam int waiveFee If set to 1 and the request is made by a club leader, the join fee will be waived for the member being invited
|
||||
* @throws 1C386/7 INVALID_ID The club does not exist or the authorized user does not have permission to add members to it
|
||||
* @throws 1C386/8 INVALID_MEMBER The member to be added could not be found or the member cannot join the club
|
||||
* @return array
|
||||
* @apiresponse \IPS\Member owner Club owner
|
||||
* @apiresponse [\IPS\Member] members Club members
|
||||
* @apiresponse [\IPS\Member] leaders Club leaders
|
||||
* @apiresponse [\IPS\Member] moderators Club moderators
|
||||
* @note If the member already exists they will be updated. This can be used to ban a member from a club or promote a member to a leader, for instance.
|
||||
* @note If using an API key, the id parameter is required and will indicate the member being added to the club. If using an OAuth access token and the request is made by a club leader, the id parameter is required. If the user is already a member of the club they can be moved to a different status (such as banned or moderator), and if the user is not a member of the club, they will be invited (and the waiveFee parameter can be passed to bypass any club joining fee). If using an OAuth access token and no id is passed, a request to join will be submitted if necessary, or they will be added to the club (the status parameter is ignored). Finally, if using an OAuth access token and an id parameter is provided, an invitation will be sent to that user.
|
||||
*/
|
||||
public function POSTitem_members( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
|
||||
if( $this->member AND isset( \IPS\Request::i()->id ) )
|
||||
{
|
||||
$member = \IPS\Member::load( \IPS\Request::i()->id );
|
||||
}
|
||||
else
|
||||
{
|
||||
$member = $this->member ?: \IPS\Member::load( \IPS\Request::i()->id );
|
||||
}
|
||||
|
||||
/* Do we have the member? */
|
||||
if( !$member OR !$member->member_id )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_MEMBER', '1C386/8', 404 );
|
||||
}
|
||||
|
||||
$currentStatus = $club->memberStatus( $member );
|
||||
|
||||
/* If this is an API key request, just do it */
|
||||
if( !$this->member )
|
||||
{
|
||||
$newStatus = \IPS\Request::i()->status ?: \IPS\Member\Club::STATUS_MEMBER;
|
||||
|
||||
if( !\in_array( $newStatus, array( \IPS\Member\Club::STATUS_MEMBER, \IPS\Member\Club::STATUS_INVITED, \IPS\Member\Club::STATUS_REQUESTED, \IPS\Member\Club::STATUS_INVITED_BYPASSING_PAYMENT, \IPS\Member\Club::STATUS_WAITING_PAYMENT, \IPS\Member\Club::STATUS_EXPIRED, \IPS\Member\Club::STATUS_EXPIRED_MODERATOR, \IPS\Member\Club::STATUS_DECLINED, \IPS\Member\Club::STATUS_BANNED, \IPS\Member\Club::STATUS_MODERATOR, \IPS\Member\Club::STATUS_LEADER ) ) )
|
||||
{
|
||||
$newStatus = \IPS\Member\Club::STATUS_MEMBER;
|
||||
}
|
||||
|
||||
$club->addMember( $member, $newStatus, TRUE );
|
||||
}
|
||||
/* If this is an OAuth member request for their _own_ account, we need to check joining fees, etc. */
|
||||
elseif( $this->member AND $member == $this->member )
|
||||
{
|
||||
if( !$club->canJoin( $member ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'CANNOT_JOIN', '1C386/M', 403 );
|
||||
}
|
||||
|
||||
/* If this is an open club, or the member was invited, or they have mod access anyway go ahead and add them */
|
||||
if ( \in_array( $currentStatus, array( \IPS\Member\Club::STATUS_INVITED, \IPS\Member\Club::STATUS_INVITED_BYPASSING_PAYMENT, \IPS\Member\Club::STATUS_WAITING_PAYMENT ) ) or $club->type === \IPS\Member\Club::TYPE_OPEN or $member->modPermission('can_access_all_clubs') )
|
||||
{
|
||||
/* Unless they have to pay */
|
||||
if ( $club->isPaid() and $currentStatus !== \IPS\Member\Club::STATUS_INVITED_BYPASSING_PAYMENT )
|
||||
{
|
||||
if ( $club->joiningFee() )
|
||||
{
|
||||
$club->generateInvoice( \IPS\nexus\Customer::load( $member->member_id ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$club->addMember( $member, \IPS\Member\Club::STATUS_MEMBER, TRUE, NULL, NULL, TRUE );
|
||||
$club->recountMembers();
|
||||
}
|
||||
}
|
||||
/* Otherwise, add the request */
|
||||
else
|
||||
{
|
||||
$club->addMember( $member, \IPS\Member\Club::STATUS_REQUESTED, TRUE, ( $this->member AND $this->member != $member ) ? $this->member : NULL );
|
||||
}
|
||||
}
|
||||
/* If this is an OAuth request for someone else's account and we are not a leader, then we should send an invite */
|
||||
elseif( $this->member AND $member != $this->member AND !$club->isLeader( $this->member ) )
|
||||
{
|
||||
if ( !$club->canInvite( $this->member ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'CANNOT_INVITE', '1C386/N', 403 );
|
||||
}
|
||||
|
||||
$club->addMember( $member, $club::STATUS_INVITED, TRUE, $member, $this->member, TRUE );
|
||||
$club->sendInvitation( $this->member, array( $member ) );
|
||||
}
|
||||
/* And finally, if this is an OAuth request for someone else's account and we are the leader, we should either send an invite or promote the member if the status is mod/leader */
|
||||
elseif( $this->member AND $member != $this->member AND $club->isLeader( $this->member ) )
|
||||
{
|
||||
/* If the member is not currently a part of the club, send an invite */
|
||||
if( $currentStatus === NULL )
|
||||
{
|
||||
$status = $club::STATUS_INVITED;
|
||||
if ( $club->isPaid() and \IPS\Request::i()->waiveFee )
|
||||
{
|
||||
$status = $club::STATUS_INVITED_BYPASSING_PAYMENT;
|
||||
}
|
||||
|
||||
$club->addMember( $member, $status, TRUE, $this->member, $this->member, TRUE );
|
||||
$club->sendInvitation( $this->member, array( $member ) );
|
||||
}
|
||||
elseif( !\in_array( \IPS\Request::i()->status, array( \IPS\Member\Club::STATUS_REQUESTED, \IPS\Member\Club::STATUS_INVITED, \IPS\Member\Club::STATUS_INVITED_BYPASSING_PAYMENT ) ) )
|
||||
{
|
||||
$club->addMember( $member, \IPS\Request::i()->status, TRUE, $this->member, $this->member, TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'CANNOT_INVITE', '1C386/O', 403 );
|
||||
}
|
||||
}
|
||||
|
||||
$club->recountMembers();
|
||||
|
||||
return $this->GETitem_members( $id );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/7', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /core/clubs/{id}/members/{member}
|
||||
* Remove a member from a club
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @param int $memberId Member (ID) to remove
|
||||
* @throws 1C386/A INVALID_ID The club does not exist or the current authorized member is not a leader of the club
|
||||
* @throws 1C386/9 INVALID_MEMBER The member to be deleted could not be found
|
||||
* @apiresponse \IPS\Member owner Club owner
|
||||
* @apiresponse [\IPS\Member] members Club members
|
||||
* @apiresponse [\IPS\Member] leaders Club leaders
|
||||
* @apiresponse [\IPS\Member] moderators Club moderators
|
||||
* @return array
|
||||
*/
|
||||
public function DELETEitem_members( $id, $memberId = 0 )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
|
||||
if( $this->member AND !$club->isLeader( $this->member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$member = \IPS\Member::load( $memberId );
|
||||
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_MEMBER', '1C386/9', 404 );
|
||||
}
|
||||
|
||||
$club->removeMember( $member );
|
||||
$club->recountMembers();
|
||||
|
||||
/* Cancel purchase */
|
||||
if ( \IPS\Application::appIsEnabled('nexus') and \IPS\Settings::i()->clubs_paid_on )
|
||||
{
|
||||
foreach ( \IPS\core\extensions\nexus\Item\ClubMembership::getPurchases( \IPS\nexus\Customer::load( $member->member_id ), $club->id ) as $purchase )
|
||||
{
|
||||
$purchase->cancelled = TRUE;
|
||||
$purchase->member->log( 'purchase', array( 'type' => 'cancel', 'id' => $purchase->id, 'name' => $purchase->name, 'by' => 'api' ) );
|
||||
$purchase->can_reactivate = FALSE;
|
||||
$purchase->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->GETitem_members( $id );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/A', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/clubs/contenttypes
|
||||
* Get content types that can be created in clubs
|
||||
*
|
||||
* @apiparam int memberId Restrict the returned types to only those that can be created by the supplied member ID
|
||||
* @note For requests using an OAuth Access Token for a particular member, the memberId parameter is ignored and the authorized member is checked
|
||||
* @apiresponse array contentTypes Available content types
|
||||
* @return array
|
||||
*/
|
||||
public function GETcontenttypes()
|
||||
{
|
||||
$member = $this->member ?: ( ( isset( \IPS\Request::i()->memberId ) ) ? \IPS\Member::load( \IPS\Request::i()->memberId ) : NULL );
|
||||
|
||||
return new \IPS\Api\Response( 200, array( 'contentTypes' => \IPS\Member\Club::availableNodeTypes( $member ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/clubs/{id}/nodes
|
||||
* Get nodes belonging to a particular club
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @throws 1C386/6 INVALID_ID The club does not exist or the authorized user does not have permission to view it
|
||||
* @apiresponse [\IPS\Node\Model] nodes Club nodes
|
||||
* @return array
|
||||
*/
|
||||
public function GETitem_nodes( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
|
||||
if( $this->member AND !$club->canView( $this->member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Format in a useful manner */
|
||||
$nodes = array();
|
||||
|
||||
foreach( $club->nodes() as $node )
|
||||
{
|
||||
$class = $node['node_class'];
|
||||
$node = $class::load( $node['node_id'] );
|
||||
|
||||
if( $node->canView( $this->member ?: NULL ) )
|
||||
{
|
||||
$nodes[] = $node->apiOutput();
|
||||
}
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 200, array( 'nodes' => $nodes ) );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/B', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /core/clubs/{id}/nodes
|
||||
* Delete a node from a club
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @reqapiparam int id Node (ID) to remove
|
||||
* @reqapiparam string class Node (class) to remove
|
||||
* @throws 1C386/H INVALID_ID The club does not exist or the current authorized member is not a leader of the club
|
||||
* @throws 1C386/G INVALID_NODE The node to be deleted could not be found or the authorized user does not have permission to delete it
|
||||
* @return null
|
||||
*/
|
||||
public function DELETEitem_nodes( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $id );
|
||||
|
||||
if( $this->member AND !$club->isLeader( $this->member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$class = \IPS\Request::i()->class;
|
||||
$node = $class::load( \IPS\Request::i()->id );
|
||||
|
||||
if ( !$node->club() or $node->club()->id !== $club->id )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Permission check */
|
||||
if( $this->member )
|
||||
{
|
||||
$itemClass = $node::$contentItemClass;
|
||||
if ( !$node->modPermission( 'delete', $this->member ) and $itemClass::contentCount( $node, TRUE, TRUE, TRUE, 1 ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_NODE', '1C386/G', 404 );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->delete( 'core_clubs_node_map', array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $class, $node->_id ) );
|
||||
$node->deleteOrMoveFormSubmit( array() );
|
||||
|
||||
return new \IPS\Api\Response( 200, NULL );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C386/H', 404 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* 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' );
|
||||
exit;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* 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' );
|
||||
exit;
|
||||
|
||||
Whitespace-only changes.
@@ -12,7 +12,7 @@
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* 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' );
|
||||
exit;
|
||||
@@ -36,9 +36,12 @@ class _me extends \IPS\Api\Controller
|
||||
|
||||
if ( $this->canAccess( 'core', 'me', 'GETitem' ) )
|
||||
{
|
||||
$output['email'] = $this->member->email;
|
||||
$output['email'] = $this->member->email;
|
||||
}
|
||||
|
||||
$output['lastVisit'] = $this->member->last_visit ? \IPS\DateTime::ts( $this->member->last_visit )->rfc3339() : NULL;
|
||||
$output['lastPost'] = $this->member->member_last_post ? \IPS\DateTime::ts( $this->member->member_last_post )->rfc3339() : NULL;
|
||||
|
||||
return new \IPS\Api\Response( 200, $output );
|
||||
}
|
||||
|
||||
@@ -47,6 +50,7 @@ class _me extends \IPS\Api\Controller
|
||||
* Get authorized user's email address
|
||||
*
|
||||
* @apimemberonly
|
||||
* @param string $path Requested path
|
||||
* @return array
|
||||
* @apiresponse string email Email address
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* 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' );
|
||||
exit;
|
||||
@@ -26,11 +26,13 @@ class _members extends \IPS\Api\Controller
|
||||
* GET /core/members
|
||||
* Get list of members
|
||||
*
|
||||
* @apiparam string sortBy What to sort by. Can be 'joined', 'name' or leave unspecified for ID
|
||||
* @apiparam string sortBy What to sort by. Can be 'joined', 'name', 'last_activity' or leave unspecified for ID
|
||||
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
|
||||
* @apiparam string name (Partial) user name to search for
|
||||
* @apiparam string email (Partial) Email address to search for
|
||||
* @apiparam int|array group Group ID or IDs to search for
|
||||
* @apiparam int activity_after Find members that have been active since unix timestamp
|
||||
* @apiparam int activity_before Find members that have been active before unix timestamp
|
||||
* @apiparam int page Page number
|
||||
* @apiparam int perPage Number of results per page - defaults to 25
|
||||
* @return \IPS\Api\PaginatedResponse<IPS\Member>
|
||||
@@ -38,35 +40,45 @@ class _members extends \IPS\Api\Controller
|
||||
public function GETindex()
|
||||
{
|
||||
/* Where clause */
|
||||
$where = array( array( 'core_members.name<>?', '' ) );
|
||||
$where = array( array( 'core_members.email<>?', '' ) );
|
||||
|
||||
/* Are we searching? */
|
||||
if( isset( \IPS\Request::i()->name ) )
|
||||
{
|
||||
$where[] = array( "name LIKE CONCAT( '%', ?, '%' )", \IPS\Request::i()->name );
|
||||
$where[] = \IPS\Db::i()->like( 'name', \IPS\Request::i()->name );
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->email ) )
|
||||
{
|
||||
$where[] = array( "email LIKE CONCAT( '%', ?, '%' )", \IPS\Request::i()->email );
|
||||
$where[] = \IPS\Db::i()->like( 'email', \IPS\Request::i()->email );
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->activity_after ) )
|
||||
{
|
||||
$where[] = array( 'last_activity > ?', \IPS\Request::i()->activity_after );
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->activity_before ) )
|
||||
{
|
||||
$where[] = array( 'last_activity < ?', \IPS\Request::i()->activity_before );
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->group ) )
|
||||
{
|
||||
if( is_array( \IPS\Request::i()->group ) )
|
||||
if( \is_array( \IPS\Request::i()->group ) )
|
||||
{
|
||||
$groups = array_map( function( $value ){ return intval( $value ); }, \IPS\Request::i()->group );
|
||||
$groups = array_map( function( $value ){ return \intval( $value ); }, \IPS\Request::i()->group );
|
||||
$where[] = array( "(member_group_id IN(" . implode( ',', $groups ) . ") OR " . \IPS\Db::i()->findInSet( 'mgroup_others', $groups ) . ")" );
|
||||
}
|
||||
elseif( \IPS\Request::i()->group )
|
||||
{
|
||||
$where[] = array( "(member_group_id=" . intval( \IPS\Request::i()->group ) . ") OR (" . \IPS\Db::i()->findInSet( 'mgroup_others', array( intval( \IPS\Request::i()->group ) ) ) . ")" );
|
||||
$where[] = array( "(member_group_id=" . \intval( \IPS\Request::i()->group ) . ") OR (" . \IPS\Db::i()->findInSet( 'mgroup_others', array( \intval( \IPS\Request::i()->group ) ) ) . ")" );
|
||||
}
|
||||
}
|
||||
|
||||
/* Sort */
|
||||
$sortBy = ( isset( \IPS\Request::i()->sortBy ) and in_array( \IPS\Request::i()->sortBy, array( 'name', 'joined' ) ) ) ? \IPS\Request::i()->sortBy : 'member_id';
|
||||
$sortDir = ( isset( \IPS\Request::i()->sortDir ) and in_array( mb_strtolower( \IPS\Request::i()->sortDir ), array( 'asc', 'desc' ) ) ) ? \IPS\Request::i()->sortDir : 'asc';
|
||||
$sortBy = ( isset( \IPS\Request::i()->sortBy ) and \in_array( \IPS\Request::i()->sortBy, array( 'name', 'joined', 'last_activity' ) ) ) ? \IPS\Request::i()->sortBy : 'member_id';
|
||||
$sortDir = ( isset( \IPS\Request::i()->sortDir ) and \in_array( mb_strtolower( \IPS\Request::i()->sortDir ), array( 'asc', 'desc' ) ) ) ? \IPS\Request::i()->sortDir : 'asc';
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Api\PaginatedResponse(
|
||||
@@ -160,7 +172,7 @@ class _members extends \IPS\Api\Controller
|
||||
}
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->secondaryGroups ) AND is_array( \IPS\Request::i()->secondaryGroups ) )
|
||||
if( isset( \IPS\Request::i()->secondaryGroups ) AND \is_array( \IPS\Request::i()->secondaryGroups ) )
|
||||
{
|
||||
foreach( \IPS\Request::i()->secondaryGroups as $groupId )
|
||||
{
|
||||
@@ -186,7 +198,7 @@ class _members extends \IPS\Api\Controller
|
||||
$member->ip_address = \IPS\Request::i()->registrationIpAddress;
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->rawProperties ) AND is_array( \IPS\Request::i()->rawProperties ) )
|
||||
if( isset( \IPS\Request::i()->rawProperties ) AND \is_array( \IPS\Request::i()->rawProperties ) )
|
||||
{
|
||||
foreach( \IPS\Request::i()->rawProperties as $property => $value )
|
||||
{
|
||||
@@ -202,12 +214,12 @@ class _members extends \IPS\Api\Controller
|
||||
{
|
||||
$logPasswordChange = FALSE;
|
||||
}
|
||||
$member->setLocalPassword( \IPS\Request::i()->password );
|
||||
$member->setLocalPassword( \IPS\Request::i()->protect('password') );
|
||||
$member->save();
|
||||
|
||||
if ( $logPasswordChange )
|
||||
{
|
||||
$member->memberSync( 'onPassChange', array( \IPS\Request::i()->password ) );
|
||||
$member->memberSync( 'onPassChange', array( \IPS\Request::i()->protect('password') ) );
|
||||
$member->logHistory( 'core', 'password_change', 'api' );
|
||||
}
|
||||
|
||||
@@ -247,7 +259,7 @@ class _members extends \IPS\Api\Controller
|
||||
}
|
||||
|
||||
/* If \IPS\Db::i()->select()->first() has only one column, then the contents of that column is returned. We do not want this here. */
|
||||
if ( !is_array( $profileFields ) )
|
||||
if ( !\is_array( $profileFields ) )
|
||||
{
|
||||
$profileFields = array();
|
||||
}
|
||||
@@ -275,7 +287,7 @@ class _members extends \IPS\Api\Controller
|
||||
* @apiclientonly
|
||||
* @apiparam string name Username
|
||||
* @apiparam string email Email address
|
||||
* @apiparam string password Password (standard login handler only)
|
||||
* @apiparam string password Password (standard login handler only). If not provided, the member will be emailed to set one.
|
||||
* @apiparam int group Group ID number
|
||||
* @apiparam string registrationIpAddress IP Address
|
||||
* @apiparam array secondaryGroups Secondary group IDs, or empty value to reset secondary groups
|
||||
@@ -287,7 +299,6 @@ class _members extends \IPS\Api\Controller
|
||||
* @throws 1C292/6 INVALID_GROUP The group ID provided is not valid
|
||||
* @throws 1C292/7 INVALID_GROUP A secondary group ID provided is not valid
|
||||
* @throws 1C292/8 NO_USERNAME_OR_EMAIL No Username or Email Address was provided for the account
|
||||
* @throws 1C292/9 NO_PASSWORD No password was provided for the account
|
||||
* @return \IPS\Member
|
||||
*/
|
||||
public function POSTindex()
|
||||
@@ -298,17 +309,24 @@ class _members extends \IPS\Api\Controller
|
||||
throw new \IPS\Api\Exception( 'NO_USERNAME_OR_EMAIL', '1C292/8', 403 );
|
||||
}
|
||||
|
||||
/* This is required as there is no other way to allow the account to be authenticated when it is created via the API */
|
||||
if ( !isset( \IPS\Request::i()->password ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PASSWORD', '1C292/9', 403 );
|
||||
}
|
||||
|
||||
$member = new \IPS\Member;
|
||||
$member->member_group_id = \IPS\Settings::i()->member_group;
|
||||
$member->members_bitoptions['created_externally'] = TRUE;
|
||||
|
||||
$forcePass = FALSE;
|
||||
if ( !isset( \IPS\Request::i()->password ) )
|
||||
{
|
||||
$member->members_bitoptions['password_reset_forced'] = TRUE;
|
||||
$forcePass = TRUE;
|
||||
}
|
||||
|
||||
$member = $this->_createOrUpdate( $member );
|
||||
|
||||
if ( $forcePass AND isset( \IPS\Request::i()->name ) AND isset( \IPS\Request::i()->email ) )
|
||||
{
|
||||
$passSetKey = md5( \IPS\SUITE_UNIQUE_KEY . \IPS\Request::i()->email . \IPS\Request::i()->name );
|
||||
\IPS\Email::buildFromTemplate( 'core', 'admin_reg', array( $member, $forcePass, $passSetKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 201, $member->apiOutput( $this->member ) );
|
||||
}
|
||||
@@ -372,8 +390,10 @@ class _members extends \IPS\Api\Controller
|
||||
* Deletes a member
|
||||
*
|
||||
* @apiclientonly
|
||||
* @param int $id ID Number
|
||||
* @throws 1C292/2 INVALID_ID The member ID does not exist
|
||||
* @apiparam string contentAction delete|hide|leave
|
||||
* @apiparam bool contentAnonymize Keep member name or anonymize content
|
||||
* @param int $id ID Number
|
||||
* @throws 1C292/2 INVALID_ID The member ID does not exist
|
||||
* @return void
|
||||
*/
|
||||
public function DELETEitem( $id )
|
||||
@@ -385,9 +405,26 @@ class _members extends \IPS\Api\Controller
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$member->delete();
|
||||
|
||||
|
||||
if( \IPS\Request::i()->contentAction )
|
||||
{
|
||||
switch( \IPS\Request::i()->contentAction )
|
||||
{
|
||||
case 'delete':
|
||||
$member->hideOrDeleteAllContent( 'delete' );
|
||||
$member->delete( FALSE );
|
||||
break;
|
||||
case 'hide':
|
||||
case 'leave':
|
||||
$member->delete( TRUE, (bool) \IPS\Request::i()->contentAnonymize ?: FALSE );
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$member->delete();
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 200, NULL );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
@@ -507,7 +544,7 @@ class _members extends \IPS\Api\Controller
|
||||
|
||||
$follow->is_anon = ( isset( \IPS\Request::i()->followAnon ) ) ? (int) \IPS\Request::i()->followAnon : 0;
|
||||
$follow->notify_do = ( isset( \IPS\Request::i()->followType ) AND \IPS\Request::i()->followType == 'none' ) ? 0 : ( ( isset( \IPS\Request::i()->followNotify ) ) ? (int) \IPS\Request::i()->followNotify : 1 );
|
||||
$follow->notify_freq = ( isset( \IPS\Request::i()->followType ) AND in_array( \IPS\Request::i()->followType, array( 'none', 'immediate', 'daily', 'weekly' ) ) ) ? \IPS\Request::i()->followType : 'immediate';
|
||||
$follow->notify_freq = ( isset( \IPS\Request::i()->followType ) AND \in_array( \IPS\Request::i()->followType, array( 'none', 'immediate', 'daily', 'weekly' ) ) ) ? \IPS\Request::i()->followType : 'immediate';
|
||||
$follow->save();
|
||||
|
||||
/* If we're following a club, follow all nodes in the club automatically */
|
||||
@@ -551,17 +588,17 @@ class _members extends \IPS\Api\Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /core/members/{id}/follows
|
||||
* DELETE /core/members/{id}/follows/{followKey}
|
||||
* Delete a follow for the member
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @apiparam string followKey Follow Key
|
||||
* @param string $followKey Follow Key
|
||||
* @throws 2C292/C INVALID_ID The member could not be found
|
||||
* @throws 2C292/E INVALID_FOLLOW_KEY The follow does not exist or does not belong to this member
|
||||
* @throws 2C292/D NO_PERMISSION The authorized user does not have permission to delete the follow
|
||||
* @return void
|
||||
*/
|
||||
public function DELETEitem_follows( $id )
|
||||
public function DELETEitem_follows( $id, $followKey='' )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -581,12 +618,12 @@ class _members extends \IPS\Api\Controller
|
||||
/* Load our follow, and make sure it belongs to the specified member */
|
||||
try
|
||||
{
|
||||
if( !isset( \IPS\Request::i()->followKey ) )
|
||||
if( !$followKey )
|
||||
{
|
||||
throw new \UnderflowException;
|
||||
}
|
||||
|
||||
$follow = \IPS\Db::i()->select( '*', 'core_follow', array( 'follow_id=?', \IPS\Request::i()->followKey ) )->first();
|
||||
$follow = \IPS\Db::i()->select( '*', 'core_follow', array( 'follow_id=?', $followKey ) )->first();
|
||||
|
||||
if( $follow['follow_member_id'] != $member->member_id )
|
||||
{
|
||||
@@ -599,7 +636,7 @@ class _members extends \IPS\Api\Controller
|
||||
}
|
||||
|
||||
/* Unfollow */
|
||||
\IPS\Db::i()->delete( 'core_follow', array( 'follow_id=?', \IPS\Request::i()->followKey ) );
|
||||
\IPS\Db::i()->delete( 'core_follow', array( 'follow_id=?', $followKey ) );
|
||||
|
||||
/* If this is a club, unfollow all nodes in the club too */
|
||||
if( $follow['follow_app'] == 'core' AND $follow['follow_area'] == 'club' )
|
||||
@@ -629,4 +666,408 @@ class _members extends \IPS\Api\Controller
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/C', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/members/{id}/notifications
|
||||
* Get list of notifications for a member
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @apiparam int page Page number
|
||||
* @apiparam int perPage Number of results per page - defaults to 25
|
||||
* @return \IPS\Api\PaginatedResponse<IPS\Notification\Inline>
|
||||
* @throws 2C292/K NO_PERMISSION The authorized user does not have permission to view the follows
|
||||
* @throws 2C292/L INVALID_ID The member could not be found
|
||||
*/
|
||||
public function GETitem_notifications( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Load member */
|
||||
$member = \IPS\Member::load( $id );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* We can only fetch notifications for ourself, if we are an authorized member */
|
||||
if ( $this->member and $member->member_id != $this->member->member_id )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/K', 403 );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Api\PaginatedResponse(
|
||||
200,
|
||||
\IPS\Db::i()->select( '*', 'core_notifications', array( '`member`=?', $member->member_id ), "updated_time DESC" ),
|
||||
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
|
||||
'IPS\Notification\Inline',
|
||||
\IPS\Db::i()->select( 'COUNT(*)', 'core_notifications', array( '`member`=?', $member->member_id ) )->first(),
|
||||
$this->member,
|
||||
isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : NULL
|
||||
);
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/L', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/members/{id}/warnings
|
||||
* Get list of warnings for a member
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @apiparam int page Page number
|
||||
* @apiparam int perPage Number of results per page - defaults to 25
|
||||
* @return \IPS\Api\PaginatedResponse<IPS\core\Warnings\Warning>
|
||||
* @throws 2C292/M NO_PERMISSION The authorized user does not have permission to view the warnings
|
||||
* @throws 2C292/N INVALID_ID The member could not be found
|
||||
*/
|
||||
public function GETitem_warnings( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Load member */
|
||||
$member = \IPS\Member::load( $id );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* We can only view warnings for ourself, if we are an authorized member */
|
||||
if ( $this->member and ( $member->member_id != $this->member->member_id OR !\IPS\Settings::i()->warn_show_own ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/M', 403 );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Api\PaginatedResponse(
|
||||
200,
|
||||
\IPS\Db::i()->select( '*', 'core_members_warn_logs', array( 'wl_member=?', $member->member_id ), "wl_date ASC" ),
|
||||
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
|
||||
'IPS\core\Warnings\Warning',
|
||||
\IPS\Db::i()->select( 'COUNT(*)', 'core_members_warn_logs', array( 'wl_member=?', $member->member_id ) )->first(),
|
||||
$this->member,
|
||||
isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : NULL
|
||||
);
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/N', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/members/{id}/warning/{warning}
|
||||
* Get a specific warning for a member
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @param int $warning Warning ID
|
||||
* @apiparam int page Page number
|
||||
* @apiparam int perPage Number of results per page - defaults to 25
|
||||
* @return \IPS\core\Warnings\Warning
|
||||
* @throws 2C292/T NO_PERMISSION The authorized user does not have permission to view the warning
|
||||
* @throws 2C292/U INVALID_ID The member could not be found
|
||||
* @throws 2C292/V INVALID_WARNING The warning could not be found
|
||||
*/
|
||||
public function GETitem_warning( $id, $warning = 0 )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Load member */
|
||||
$member = \IPS\Member::load( $id );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$warning = \IPS\core\Warnings\Warning::load( $warning );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_WARNING', '2C292/V', 404 );
|
||||
}
|
||||
|
||||
/* We can only view warnings for ourself, if we are an authorized member */
|
||||
if ( $this->member and ( $member->member_id != $this->member->member_id OR !\IPS\Settings::i()->warn_show_own OR $warning->member != $member->member_id ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/T', 403 );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Api\Response( 200, $warning->apiOutput( $this->member ) );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/U', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /core/members/{id}/warnings
|
||||
* Store a new warning for the member
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @reqapiparam int|null moderator Member ID of the moderator to issue the warning from
|
||||
* @apiparam int|null reason Warn reason to use for this warning
|
||||
* @apiparam int|null points Points to issue for the warning. Will use the points from the reason if not specified and a reason is, or if the reason does not allow points to be overridden.
|
||||
* @apiparam string|null memberNote Note to display to the member as HTML (e.g. "<p>This is a comment.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
|
||||
* @apiparam string|null moderatorNote Note to display to moderators as HTML (e.g. "<p>This is a comment.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
|
||||
* @apiparam bool acknowledged Whether the warning should be considered acknowledged by the member or not
|
||||
* @apiparam datetime|int|null modQueue Date to place in moderator queue until or -1 to place in moderator queue indefinitely. NULL to not place member in moderator queue.
|
||||
* @apiparam datetime|int|null restrictPosts Date to restrict posts until or -1 to restrict posts indefinitely. NULL to not restrict posts.
|
||||
* @apiparam datetime|int|null suspend Date to suspend member until or -1 to suspend indefinitely. NULL to not suspend.
|
||||
* @apiparam datetime|int|null expire Date to expire warn points after or -1 to not expire warn points. Will use the warn points expiration from the warn reason if not specified and a reason is, or if the reason does not allow warn point removal to be overridden. NULL to not expire.
|
||||
* @return \IPS\core\Warnings\Warning
|
||||
* @throws 2C292/G NO_PERMISSION The authorized user does not have permission to warn the member
|
||||
* @throws 2C292/O INVALID_ID The member could not be found
|
||||
* @throws 1C292/Y MODERATOR_REQUIRED When not using an OAuth access token a moderator member ID must be supplied
|
||||
* @note The warning will be issued as the current authorized user if using an OAuth access token, otherwise the 'moderator' parameter must be specified to indicate which moderator the warning should be issued from
|
||||
*/
|
||||
public function POSTitem_warnings( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Load member */
|
||||
$member = \IPS\Member::load( $id );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Make sure we can warn */
|
||||
if ( $this->member and !$this->member->canWarn( $member ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/R', 403 );
|
||||
}
|
||||
|
||||
/* Make sure we have a moderator */
|
||||
if( !$this->member AND ( !\IPS\Request::i()->moderator OR !\IPS\Member::load( \IPS\Request::i()->moderator )->member_id ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'MODERATOR_REQUIRED', '1C292/Y', 401 );
|
||||
}
|
||||
|
||||
/* Start the warning with the easy stuff */
|
||||
$warning = new \IPS\core\Warnings\Warning;
|
||||
$warning->date = time();
|
||||
$warning->member = $member->member_id;
|
||||
$warning->moderator = $this->member ? $this->member->member_id : \IPS\Member::load( \IPS\Request::i()->moderator )->member_id;
|
||||
|
||||
$options = array(
|
||||
'warn_points' => (int) \IPS\Request::i()->points,
|
||||
'warn_reason' => \IPS\Request::i()->reason,
|
||||
'warn_member_note' => \IPS\Request::i()->memberNote,
|
||||
'warn_mod_note' => \IPS\Request::i()->modertorNote,
|
||||
'warn_punishment' => array(),
|
||||
'warn_remove' => NULL,
|
||||
);
|
||||
|
||||
if ( $this->member )
|
||||
{
|
||||
$options['warn_member_note'] = \IPS\Text\Parser::parseStatic( $options['warn_member_note'], TRUE, NULL, $this->member, 'core_Modcp' );
|
||||
$options['warn_mod_note'] = \IPS\Text\Parser::parseStatic( $options['warn_mod_note'], TRUE, NULL, $this->member, 'core_Modcp' );
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->expire ) )
|
||||
{
|
||||
if( \IPS\Request::i()->expire == -1 )
|
||||
{
|
||||
$options['warn_remove'] = \IPS\Request::i()->expire;
|
||||
}
|
||||
else
|
||||
{
|
||||
$options['warn_remove'] = new \IPS\DateTime( \IPS\Request::i()->expire );
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Request::i()->modQueue )
|
||||
{
|
||||
if( \IPS\Request::i()->modQueue == -1 )
|
||||
{
|
||||
$options['warn_punishment']['mq'] = \IPS\Request::i()->modQueue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$options['warn_punishment']['mq'] = new \IPS\DateTime( \IPS\Request::i()->modQueue );
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Request::i()->restrictPosts )
|
||||
{
|
||||
if( \IPS\Request::i()->restrictPosts == -1 )
|
||||
{
|
||||
$options['warn_punishment']['rpa'] = \IPS\Request::i()->restrictPosts;
|
||||
}
|
||||
else
|
||||
{
|
||||
$options['warn_punishment']['rpa'] = new \IPS\DateTime( \IPS\Request::i()->restrictPosts );
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Request::i()->suspend )
|
||||
{
|
||||
if( \IPS\Request::i()->suspend == -1 )
|
||||
{
|
||||
$options['warn_punishment']['suspend'] = \IPS\Request::i()->suspend;
|
||||
}
|
||||
else
|
||||
{
|
||||
$options['warn_punishment']['suspend'] = new \IPS\DateTime( \IPS\Request::i()->suspend );
|
||||
}
|
||||
}
|
||||
|
||||
$options = $warning->processWarning( $options, \IPS\Member::load( $warning->moderator ), TRUE );
|
||||
|
||||
if( \IPS\Settings::i()->warnings_acknowledge AND isset( \IPS\Request::i()->acknowledged ) )
|
||||
{
|
||||
$warning->acknowledged = \IPS\Request::i()->acknowledged;
|
||||
}
|
||||
|
||||
if( !$warning->expire_date )
|
||||
{
|
||||
$warning->expire_date = -1;
|
||||
}
|
||||
|
||||
$warning->save();
|
||||
|
||||
/* Now apply the consequences */
|
||||
if ( $warning->points )
|
||||
{
|
||||
$member->warn_level += $warning->points;
|
||||
}
|
||||
$consequences = array();
|
||||
foreach ( array( 'mq' => 'mod_posts', 'rpa' => 'restrict_post', 'suspend' => 'temp_ban' ) as $k => $v )
|
||||
{
|
||||
if ( $warning->$k )
|
||||
{
|
||||
$consequences[ $v ] = $warning->$k;
|
||||
if ( $warning->$k != -1 )
|
||||
{
|
||||
$member->$v = \IPS\DateTime::create()->add( new \DateInterval( $warning->$k ) )->getTimestamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
$member->$v = $this->$k;
|
||||
}
|
||||
}
|
||||
}
|
||||
$member->members_bitoptions['unacknowledged_warnings'] = \IPS\Settings::i()->warnings_acknowledge ? (bool) !$warning->acknowledged : FALSE;
|
||||
$member->save();
|
||||
$member->logHistory( 'core', 'warning', array( 'wid' => $warning->id, 'by' => 'api', 'points' => $warning->points, 'reason' => $warning->reason, 'consequences' => $consequences ) );
|
||||
|
||||
return new \IPS\Api\Response( 200, $warning->apiOutput( $this->member ) );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/O', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /core/members/{id}/warnings/{warning}
|
||||
* Delete (undo) a warning for the member
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @param int $warningId Warning ID
|
||||
* @apiparam bool undoOnly Only undo the warning but do not delete it
|
||||
* @throws 2C292/P INVALID_ID The member could not be found
|
||||
* @throws 2C292/Q INVALID_WARNING The warning could not be loaded or the current authorized user does not have permission to delete the warning
|
||||
* @return void
|
||||
*/
|
||||
public function DELETEitem_warnings( $id, $warningId = 0 )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Load member */
|
||||
$member = \IPS\Member::load( $id );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Load the warning and then undo it */
|
||||
try
|
||||
{
|
||||
$warning = \IPS\core\Warnings\Warning::load( $warningId );
|
||||
|
||||
if( $this->member AND !$warning->canDelete( $this->member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_WARNING', '2C292/Q', 404 );
|
||||
}
|
||||
|
||||
/* Revoke the warning */
|
||||
$warning->undo();
|
||||
|
||||
if( !isset( \IPS\Request::i()->undoOnly ) OR !\IPS\Request::i()->undoOnly )
|
||||
{
|
||||
$warning->delete();
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 200, NULL );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/P', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /core/members/{id}/warnings/{warning}/acknowledge
|
||||
* Acknowledge a warning
|
||||
*
|
||||
* @param int $id ID Number
|
||||
* @param int $warningId Warning to acknowledge
|
||||
* @return \IPS\core\Warnings\Warning
|
||||
* @throws 2C292/M NO_PERMISSION The authorized user does not have permission to view the warnings
|
||||
* @throws 2C292/N INVALID_ID The member could not be found
|
||||
*/
|
||||
public function POSTitem_warnings_acknowledge( $id, $warningId = 0 )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Load member */
|
||||
$member = \IPS\Member::load( $id );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Load the warning */
|
||||
try
|
||||
{
|
||||
$warning = \IPS\core\Warnings\Warning::load( $warningId );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_WARNING', '2C292/W', 404 );
|
||||
}
|
||||
|
||||
/* We can only view warnings for ourself, if we are an authorized member */
|
||||
if ( $this->member and !$warning->canAcknowledge( $this->member ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/X', 403 );
|
||||
}
|
||||
|
||||
/* Acknowledge it */
|
||||
$warning->acknowledged = TRUE;
|
||||
$warning->save();
|
||||
|
||||
$member->members_bitoptions['unacknowledged_warnings'] = (bool) \IPS\Db::i()->select( 'COUNT(*)', 'core_members_warn_logs', array( "wl_member=? AND wl_acknowledged=0", $member->member_id ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
|
||||
$member->save();
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Api\Response( 200, $warning->apiOutput() );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/N', 404 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* 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' );
|
||||
exit;
|
||||
@@ -47,7 +47,7 @@ class _messages extends \IPS\Api\Controller
|
||||
}
|
||||
|
||||
/* Verify there are recipients and all the recipients are valid */
|
||||
if( !isset( \IPS\Request::i()->to ) OR !is_array( \IPS\Request::i()->to ) OR !count( \IPS\Request::i()->to ) )
|
||||
if( !isset( \IPS\Request::i()->to ) OR !\is_array( \IPS\Request::i()->to ) OR !\count( \IPS\Request::i()->to ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_RECIPIENT', '1C374/3', 404 );
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class _messages extends \IPS\Api\Controller
|
||||
/* Create the conversation */
|
||||
$item = \IPS\core\Messenger\Conversation::createItem( \IPS\Member::load( (int) \IPS\Request::i()->from ), \IPS\Request::i()->ipAddress(), \IPS\DateTime::create(), NULL );
|
||||
$item->title = \IPS\Request::i()->title;
|
||||
$item->to_count = count( \IPS\Request::i()->to );
|
||||
$item->to_count = \count( \IPS\Request::i()->to );
|
||||
$item->save();
|
||||
|
||||
/* Create the first message */
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* 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' );
|
||||
exit;
|
||||
@@ -36,7 +36,7 @@ class _search extends \IPS\Api\Controller
|
||||
* @apiparam int search_min_comments Minimum number of comments search results must have for content types that support comments
|
||||
* @apiparam int search_min_replies Minimum number of comments search results must have for content types that require the first comment (e.g. topics)
|
||||
* @apiparam int search_min_reviews Minimum number of reviews search results must have
|
||||
* @apiparam int search_min_views Minimum number of views search results must have
|
||||
* @apiparam int search_min_views Minimum number of views search results must have (note, not supported by elasticsearch)
|
||||
* @apiparam string author Restrict searches to results posted by this member (name)
|
||||
* @apiparam string club Comma-separated list of club IDs to restrict searches to
|
||||
* @apiparam string start_before Date period (from current time) that search results should start before
|
||||
@@ -48,6 +48,7 @@ class _search extends \IPS\Api\Controller
|
||||
* @apiparam string search_and_or Whether to perform an "and" search or an "or" search for all search terms
|
||||
* @apiparam string search_in Specify "titles" to search in titles only, otherwise titles and content are both searched
|
||||
* @apiparam int search_as Member ID to perform the search as (guest permissions will be used when this parameter is omitted)
|
||||
* @apiparam bool doNotTrack If doNotTrack is passed with a value of 1, the search will not be tracked for statistical purposes
|
||||
* @return array
|
||||
* @apiresponse int page api_int_page
|
||||
* @apiresponse int perPage api_int_perpage
|
||||
@@ -201,7 +202,7 @@ class _search extends \IPS\Api\Controller
|
||||
$flags = ( isset( \IPS\Request::i()->eitherTermsOrTags ) and \IPS\Request::i()->eitherTermsOrTags === 'and' ) ? \IPS\Content\Search\Query::TERM_AND_TAGS : \IPS\Content\Search\Query::TERM_OR_TAGS;
|
||||
$operator = NULL;
|
||||
|
||||
if ( isset( \IPS\Request::i()->search_and_or ) and in_array( \IPS\Request::i()->search_and_or, array( \IPS\Content\Search\Query::OPERATOR_OR, \IPS\Content\Search\Query::OPERATOR_AND ) ) )
|
||||
if ( isset( \IPS\Request::i()->search_and_or ) and \in_array( \IPS\Request::i()->search_and_or, array( \IPS\Content\Search\Query::OPERATOR_OR, \IPS\Content\Search\Query::OPERATOR_AND ) ) )
|
||||
{
|
||||
$operator = \IPS\Request::i()->search_and_or;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Warn Reasons API
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 5 June 2018
|
||||
*/
|
||||
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Warn Reasons API
|
||||
*/
|
||||
class _warnreasons extends \IPS\Node\Api\NodeController
|
||||
{
|
||||
/**
|
||||
* Class
|
||||
*/
|
||||
protected $class = 'IPS\core\Warnings\Reason';
|
||||
|
||||
/**
|
||||
* GET /core/warnreasons
|
||||
* Get list of warn reasons
|
||||
*
|
||||
* @apiparam int page Page number
|
||||
* @apiparam int perPage Number of results per page - defaults to 25
|
||||
* @return \IPS\Api\PaginatedResponse<IPS\core\Warnings\Reason>
|
||||
* @throws 1C292/S NO_PERMISSION The current authorized user does not have permission to issue warnings and as such cannot view the list of warn reasons
|
||||
*/
|
||||
public function GETindex()
|
||||
{
|
||||
/* Check permissions */
|
||||
if( $this->member AND ( !$this->member->modPermission('mod_can_warn') OR !$this->member->modPermission('mod_see_warn') ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_PERMISSION', '1C292/S', 403 );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $this->_list();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /core/warnreasons/{id}
|
||||
* Get specific warn reason
|
||||
*
|
||||
* @apiclientonly
|
||||
* @param int $id ID Number
|
||||
* @throws 1C385/1 INVALID_ID The warn reason does not exist
|
||||
* @return \IPS\core\Warnings\Reason
|
||||
*/
|
||||
public function GETitem( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
return $this->_view( $id );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C385/1', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /core/warnreasons
|
||||
* Create a warn reason
|
||||
*
|
||||
* @apiclientonly
|
||||
* @reqapiparam string name Name for the warn reason
|
||||
* @apiparam string defaultNotes Default notes to associate with the warn reason
|
||||
* @apiparam int points Default points to be issued with the warn reason
|
||||
* @apiparam bool pointsOverride Whether or not moderators can override the default points
|
||||
* @apiparam bool pointsAutoRemove Whether or not to automatically remove points (defaults to false). If true, you must supply removePoints.
|
||||
* @apiparam string|null removePoints Timeframe to remove points after as a date interval (e.g. P2D for 2 days or PT6H for 6 hours)
|
||||
* @apiparam bool removeOverride Whether or not moderators can override the default points removal configuration
|
||||
* @return \IPS\core\Warnings\Reason
|
||||
* @throws 1C385/2 INVALID_POINTS_EXPIRATION Points were specified as automatically removing but the removal time period was not supplied or is invalid
|
||||
* @throws 1C385/4 NO_NAME A name for the warn reason must be supplied
|
||||
*/
|
||||
public function POSTindex()
|
||||
{
|
||||
if( !\IPS\Request::i()->name )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_NAME', '1C385/4', 400 );
|
||||
}
|
||||
|
||||
if( isset( \IPS\Request::i()->pointsAutoRemove ) AND \IPS\Request::i()->pointsAutoRemove AND ( !isset( \IPS\Request::i()->removePoints ) OR !\IPS\Request::i()->removePoints ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_POINTS_EXPIRATION', '1C385/2', 400 );
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 201, $this->_create()->apiOutput( $this->member ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /core/warnreasons/{id}
|
||||
* Edit a warn reason
|
||||
*
|
||||
* @apiclientonly
|
||||
* @reqapiparam string name Name for the warn reason
|
||||
* @apiparam string defaultNotes Default notes to associate with the warn reason
|
||||
* @apiparam int points Default points to be issued with the warn reason
|
||||
* @apiparam bool pointsOverride Whether or not moderators can override the default points
|
||||
* @apiparam bool pointsAutoRemove Whether or not to automatically remove points (defaults to false). If true, you must supply removePoints.
|
||||
* @apiparam string|null removePoints Timeframe to remove points after as a date interval (e.g. P2D for 2 days or PT6H for 6 hours)
|
||||
* @apiparam bool removeOverride Whether or not moderators can override the default points removal configuration
|
||||
* @param int $id ID Number
|
||||
* @return \IPS\core\Warnings\Reason
|
||||
* @throws 1C385/3 INVALID_POINTS_EXPIRATION Points were specified as automatically removing but the removal time period was not supplied or is invalid
|
||||
*/
|
||||
public function POSTitem( $id )
|
||||
{
|
||||
if( isset( \IPS\Request::i()->pointsAutoRemove ) AND \IPS\Request::i()->pointsAutoRemove AND ( !isset( \IPS\Request::i()->removePoints ) OR !\IPS\Request::i()->removePoints ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_POINTS_EXPIRATION', '1C385/3', 400 );
|
||||
}
|
||||
|
||||
$class = $this->class;
|
||||
$reason = $class::load( $id );
|
||||
|
||||
return new \IPS\Api\Response( 200, $this->_createOrUpdate( $reason )->apiOutput( $this->member ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /core/warnreasons/{id}
|
||||
* Delete a warn reason
|
||||
*
|
||||
* @apiclientonly
|
||||
* @param int $id ID Number
|
||||
* @return void
|
||||
*/
|
||||
public function DELETEitem( $id )
|
||||
{
|
||||
return $this->_delete( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update node
|
||||
*
|
||||
* @param \IPS\node\Model $reason The node
|
||||
* @return \IPS\node\Model
|
||||
*/
|
||||
protected function _createOrUpdate( \IPS\node\Model $reason )
|
||||
{
|
||||
if( \IPS\Request::i()->name )
|
||||
{
|
||||
\IPS\Lang::saveCustom( 'core', 'core_warn_reason_' . $reason->id, \IPS\Request::i()->name );
|
||||
}
|
||||
|
||||
$reason->points = (int) \IPS\Request::i()->points;
|
||||
$reason->points_override = (bool) \IPS\Request::i()->pointsOverride;
|
||||
$reason->remove_override = (bool) \IPS\Request::i()->removeOverride;
|
||||
$reason->notes = \IPS\Request::i()->notes;
|
||||
|
||||
if( isset( \IPS\Request::i()->pointsAutoRemove ) OR isset( \IPS\Request::i()->removePoints ) )
|
||||
{
|
||||
if( !\IPS\Request::i()->pointsAutoRemove )
|
||||
{
|
||||
$reason->remove = -1;
|
||||
$reason->remove_unit = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
$reason->remove = (int) str_ireplace( array( 'P', 'T', 'H' ), '', \IPS\Request::i()->removePoints );
|
||||
|
||||
if( mb_strpos( \IPS\Request::i()->removePoints, 'PT' ) === 0 )
|
||||
{
|
||||
$reason->remove_unit = 'h';
|
||||
}
|
||||
else
|
||||
{
|
||||
$reason->remove_unit = 'd';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_createOrUpdate( $reason );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Webhooks API
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 5 Feb 2020
|
||||
*/
|
||||
|
||||
namespace IPS\core\api;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Webhooks API
|
||||
*/
|
||||
class _webhooks extends \IPS\Api\Controller
|
||||
{
|
||||
/**
|
||||
* POST /core/webhooks
|
||||
* Create a webhook
|
||||
*
|
||||
* @apiclientonly
|
||||
* @reqapiparam array events List of events to subscribe to
|
||||
* @reqapiparam string url URL to send webhook to
|
||||
* @return \IPS\Api\Webhook
|
||||
* @throws 1C293/1 NO_EVENTS No events were specified
|
||||
* @throws 1C293/2 INVALID_URL The URL specified was not valid
|
||||
*/
|
||||
public function POSTindex()
|
||||
{
|
||||
$events = \IPS\Request::i()->events;
|
||||
if ( !$events )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_EVENTS', '1C293/1', 400 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$url = new \IPS\Http\Url( \IPS\Request::i()->url );
|
||||
}
|
||||
catch ( \IPS\Http\Url\Exception $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_URL', '1C293/2', 400 );
|
||||
}
|
||||
|
||||
$webhook = new \IPS\Api\Webhook;
|
||||
$webhook->api_key = $this->apiKey;
|
||||
$webhook->events = $events;
|
||||
$webhook->filters = ( \IPS\Request::i()->filters ?: array() );
|
||||
$webhook->url = $url;
|
||||
$webhook->save();
|
||||
|
||||
return new \IPS\Api\Response( 201, $webhook->apiOutput() );
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /core/webhooks/{id}
|
||||
* Deletes a webhooks
|
||||
*
|
||||
* @apiclientonly
|
||||
* @param int $id ID Number
|
||||
* @return void
|
||||
* @throws 1C293/3 INVALID_ID The ID provided does not match any webhook
|
||||
* @throws 3C293/4 WRONG_API_KEY The API key making this request is not the API key that created the webhook
|
||||
*/
|
||||
public function DELETEitem( $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$webhook = \IPS\Api\Webhook::load( $id );
|
||||
|
||||
if ( $webhook->api_key != $this->apiKey )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'WRONG_API_KEY', '3C293/4', 403 );
|
||||
}
|
||||
|
||||
$webhook->delete();
|
||||
|
||||
return new \IPS\Api\Response( 200, NULL );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1C293/3', 404 );
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user