Version 4.5.0

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

No files matched your search

@@ -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;
}
}
+59
View File
@@ -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);
}
}