Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+8 -4
View File
@@ -26,7 +26,8 @@ class _groups extends \IPS\Api\Controller
* GET /core/groups
* Get list of groups
*
* @apiparam int page Page number
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @return \IPS\Api\PaginatedResponse<IPS\Member\Group>
*/
public function GETindex()
@@ -37,10 +38,12 @@ class _groups extends \IPS\Api\Controller
/* Return */
return new \IPS\Api\PaginatedResponse(
200,
\IPS\Db::i()->select( '*', 'core_groups', $where, "g_id asc", NULL, NULL, NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS ),
\IPS\Db::i()->select( '*', 'core_groups', $where, "g_id asc" ),
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
'IPS\Member\Group',
\IPS\Db::i()->select( 'COUNT(*)', 'core_groups', $where )->first()
\IPS\Db::i()->select( 'COUNT(*)', 'core_groups', $where )->first(),
$this->member,
isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : NULL
);
}
@@ -62,7 +65,7 @@ class _groups extends \IPS\Api\Controller
throw new \OutOfRangeException;
}
return new \IPS\Api\Response( 200, $group->apiOutput() );
return new \IPS\Api\Response( 200, $group->apiOutput( $this->member ) );
}
catch ( \OutOfRangeException $e )
{
@@ -74,6 +77,7 @@ class _groups extends \IPS\Api\Controller
* DELETE /core/groups/{id}
* Deletes a group
*
* @apiclientonly
* @param int $id ID Number
* @throws 1C358/2 INVALID_ID The group ID does not exist
* @return void
+57
View File
@@ -0,0 +1,57 @@
<?php
/**
* @brief Me API
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 3 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\core\api;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Me API
*/
class _me extends \IPS\Api\Controller
{
/**
* GET /core/me
* Get basic information about the authorized user
*
* @apimemberonly
* @return \IPS\Member
*/
public function GETindex()
{
$output = $this->member->apiOutput( $this->member );
if ( $this->canAccess( 'core', 'me', 'GETitem' ) )
{
$output['email'] = $this->member->email;
}
return new \IPS\Api\Response( 200, $output );
}
/**
* GET /core/me/email
* Get authorized user's email address
*
* @apimemberonly
* @return array
* @apiresponse string email Email address
*/
public function GETitem( $path )
{
return new \IPS\Api\Response( 200, array( 'email' => $this->member->email ) );
}
}
+368 -20
View File
@@ -26,15 +26,43 @@ class _members extends \IPS\Api\Controller
* GET /core/members
* Get list of members
*
* @apiparam string sortBy What to sort by. Can be 'joined', 'name' or leave unspecified for ID
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam int page Page number
* @apiparam string sortBy What to sort by. Can be 'joined', 'name' or leave unspecified for ID
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam string name (Partial) user name to search for
* @apiparam string email (Partial) Email address to search for
* @apiparam int|array group Group ID or IDs to search for
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @return \IPS\Api\PaginatedResponse<IPS\Member>
*/
public function GETindex()
{
/* Where clause */
$where = array();
$where = array( array( 'core_members.name<>?', '' ) );
/* Are we searching? */
if( isset( \IPS\Request::i()->name ) )
{
$where[] = array( "name LIKE CONCAT( '%', ?, '%' )", \IPS\Request::i()->name );
}
if( isset( \IPS\Request::i()->email ) )
{
$where[] = array( "email LIKE CONCAT( '%', ?, '%' )", \IPS\Request::i()->email );
}
if( isset( \IPS\Request::i()->group ) )
{
if( is_array( \IPS\Request::i()->group ) )
{
$groups = array_map( function( $value ){ return intval( $value ); }, \IPS\Request::i()->group );
$where[] = array( "(member_group_id IN(" . implode( ',', $groups ) . ") OR " . \IPS\Db::i()->findInSet( 'mgroup_others', $groups ) . ")" );
}
elseif( \IPS\Request::i()->group )
{
$where[] = array( "(member_group_id=" . intval( \IPS\Request::i()->group ) . ") OR (" . \IPS\Db::i()->findInSet( 'mgroup_others', array( intval( \IPS\Request::i()->group ) ) ) . ")" );
}
}
/* Sort */
$sortBy = ( isset( \IPS\Request::i()->sortBy ) and in_array( \IPS\Request::i()->sortBy, array( 'name', 'joined' ) ) ) ? \IPS\Request::i()->sortBy : 'member_id';
@@ -43,10 +71,12 @@ class _members extends \IPS\Api\Controller
/* Return */
return new \IPS\Api\PaginatedResponse(
200,
\IPS\Db::i()->select( '*', 'core_members', $where, "{$sortBy} {$sortDir}", NULL, NULL, NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS ),
\IPS\Db::i()->select( '*', 'core_members', $where, "{$sortBy} {$sortDir}" ),
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
'IPS\Member',
\IPS\Db::i()->select( 'COUNT(*)', 'core_members', $where )->first()
\IPS\Db::i()->select( 'COUNT(*)', 'core_members', $where )->first(),
$this->member,
isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : NULL
);
}
@@ -55,6 +85,7 @@ class _members extends \IPS\Api\Controller
* Get information about a specific member
*
* @param int $id ID Number
* @apiparam array otherFields An array of additional non-standard fields to return via the REST API
* @throws 1C292/2 INVALID_ID The member ID does not exist
* @return \IPS\Member
*/
@@ -68,7 +99,7 @@ class _members extends \IPS\Api\Controller
throw new \OutOfRangeException;
}
return new \IPS\Api\Response( 200, $member->apiOutput() );
return new \IPS\Api\Response( 200, $member->apiOutput( $this->member, ( isset( \IPS\Request::i()->otherFields ) ) ? \IPS\Request::i()->otherFields : NULL ) );
}
catch ( \OutOfRangeException $e )
{
@@ -79,8 +110,7 @@ class _members extends \IPS\Api\Controller
/**
* Create or update member
*
* @param \IPS\Member $member The member
* @apiparam int group Group ID number
* @param \IPS\Member $member The member
* @throws 1C292/4 USERNAME_EXISTS The username provided is already in use
* @throws 1C292/5 EMAIL_EXISTS The email address provided is already in use
* @throws 1C292/6 INVALID_GROUP The group ID provided is not valid
@@ -92,7 +122,8 @@ class _members extends \IPS\Api\Controller
{
$existingUsername = \IPS\Member::load( \IPS\Request::i()->name, 'name' );
if ( !$existingUsername->member_id )
{
{
$member->logHistory( 'core', 'display_name', array( 'old' => $member->name, 'new' => \IPS\Request::i()->name, 'by' => 'api' ) );
$member->name = \IPS\Request::i()->name;
}
else
@@ -106,6 +137,7 @@ class _members extends \IPS\Api\Controller
$existingEmail = \IPS\Member::load( \IPS\Request::i()->email, 'email' );
if ( !$existingEmail->member_id )
{
$member->logHistory( 'core', 'email_change', array( 'old' => $member->name, 'new' => \IPS\Request::i()->name, 'by' => 'api' ) );
$member->email = \IPS\Request::i()->email;
$member->invalidateSessionsAndLogins();
}
@@ -128,16 +160,57 @@ class _members extends \IPS\Api\Controller
}
}
if ( isset( \IPS\Request::i()->password ) )
if( isset( \IPS\Request::i()->secondaryGroups ) AND is_array( \IPS\Request::i()->secondaryGroups ) )
{
foreach ( \IPS\Login::handlers( TRUE ) as $handler )
foreach( \IPS\Request::i()->secondaryGroups as $groupId )
{
try
{
$handler->changePassword( $member, \IPS\Request::i()->password );
$group = \IPS\Member\Group::load( $groupId );
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_GROUP', '1C292/7', 403 );
}
catch( \BadMethodCallException $e ){}
}
$member->mgroup_others = implode( ',', \IPS\Request::i()->secondaryGroups );
}
elseif( isset( \IPS\Request::i()->secondaryGroups ) AND \IPS\Request::i()->secondaryGroups == '' )
{
$member->mgroup_others = '';
}
if( isset( \IPS\Request::i()->registrationIpAddress ) AND filter_var( \IPS\Request::i()->registrationIpAddress, FILTER_VALIDATE_IP ) )
{
$member->ip_address = \IPS\Request::i()->registrationIpAddress;
}
if( isset( \IPS\Request::i()->rawProperties ) AND is_array( \IPS\Request::i()->rawProperties ) )
{
foreach( \IPS\Request::i()->rawProperties as $property => $value )
{
$member->$property = $value;
}
}
if ( isset( \IPS\Request::i()->password ) )
{
/* Setting the password for the just created member shouldn't be logged to the member history and shouldn't fire the onPassChange Sync call */
$logPasswordChange = TRUE;
if ( $member->member_id )
{
$logPasswordChange = FALSE;
}
$member->setLocalPassword( \IPS\Request::i()->password );
$member->save();
if ( $logPasswordChange )
{
$member->memberSync( 'onPassChange', array( \IPS\Request::i()->password ) );
$member->logHistory( 'core', 'password_change', 'api' );
}
$member->invalidateSessionsAndLogins();
}
else
@@ -145,6 +218,21 @@ class _members extends \IPS\Api\Controller
$member->save();
}
/* Validation stuff */
if( isset( \IPS\Request::i()->validated ) )
{
/* If the member is currently validating and we are setting the validated flag to true, then complete the validation */
if( \IPS\Request::i()->validated == 1 AND $member->members_bitoptions['validating'] )
{
$member->validationComplete();
}
/* If the member is not currently validating, and we set the validated flag to false AND validation is enabled, mark the member validating */
elseif( \IPS\Request::i()->validated == 0 AND !$member->members_bitoptions['validating'] AND \IPS\Settings::i()->reg_auth_type != 'none' )
{
$member->postRegistration();
}
}
/* Any custom fields? */
if( isset( \IPS\Request::i()->customFields ) )
{
@@ -182,16 +270,22 @@ class _members extends \IPS\Api\Controller
/**
* POST /core/members
* Create a member
* Create a member. Requires the standard login handler to be enabled
*
* @apiclientonly
* @apiparam string name Username
* @apiparam string email Email address
* @apiparam string password Password
* @apiparam string password Password (standard login handler only)
* @apiparam int group Group ID number
* @apiparam string registrationIpAddress IP Address
* @apiparam array secondaryGroups Secondary group IDs, or empty value to reset secondary groups
* @apiparam object customFields Array of custom fields as fieldId => fieldValue
* @apiparam int validated Flag to indicate if the account is validated (1) or not (0)
* @apiparam array rawProperties Key => value object of member properties to set. Note that values will be set exactly as supplied without validation. USE AT YOUR OWN RISK.
* @throws 1C292/4 USERNAME_EXISTS The username provided is already in use
* @throws 1C292/5 EMAIL_EXISTS The email address provided is already in use
* @throws 1C292/6 INVALID_GROUP The group ID provided is not valid
* @throws 1C292/7 INVALID_GROUP A secondary group ID provided is not valid
* @throws 1C292/8 NO_USERNAME_OR_EMAIL No Username or Email Address was provided for the account
* @throws 1C292/9 NO_PASSWORD No password was provided for the account
* @return \IPS\Member
@@ -215,22 +309,29 @@ class _members extends \IPS\Api\Controller
$member = $this->_createOrUpdate( $member );
return new \IPS\Api\Response( 201, $member->apiOutput() );
return new \IPS\Api\Response( 201, $member->apiOutput( $this->member ) );
}
/**
* POST /core/members/{id}
* Edit a member
*
* @apiclientonly
* @apiparam string name Username
* @apiparam string email Email address
* @apiparam string password Password
* @apiparam string password Password (standard login handler only)
* @apiparam int group Group ID number
* @apiparam string registrationIpAddress IP Address
* @apiparam array secondaryGroups Secondary group IDs, or empty value to reset secondary groups
* @apiparam object customFields Array of custom fields as fieldId => fieldValue
* @apiparam int validated Flag to indicate if the account is validated (1) or not (0)
* @apiparam array rawProperties Key => value object of member properties to set. Note that values will be set exactly as supplied without validation. USE AT YOUR OWN RISK.
* @param int $id ID Number
* @throws 2C292/7 INVALID_ID The member ID does not exist
* @throws 1C292/4 USERNAME_EXISTS The username provided is already in use
* @throws 1C292/5 EMAIL_EXISTS The email address provided is already in use
* @throws 1C292/6 INVALID_GROUP The group ID provided is not valid
* @throws 1C292/7 INVALID_GROUP A secondary group ID provided is not valid
* @return \IPS\Member
*/
public function POSTitem( $id )
@@ -242,10 +343,22 @@ class _members extends \IPS\Api\Controller
{
throw new \OutOfRangeException;
}
$oldPrimaryGroup = $member->member_group_id;
$oldSecondaryGroups = array_unique( array_filter( explode( ',', $member->mgroup_others ) ) );
$member = $this->_createOrUpdate( $member );
if ( $oldPrimaryGroup != $member->member_group_id )
{
$member->logHistory( 'core', 'group', array( 'type' => 'primary', 'by' => 'api', 'apiKey' => $this->apiKey ? $this->apiKey->id : NULL, 'client' => $this->client ? $this->client->client_id : NULL, 'old' => $oldPrimaryGroup, 'new' => $member->member_group_id ), $this->member ?: FALSE );
}
$newSecondaryGroups = array_unique( array_filter( explode( ',', $member->mgroup_others ) ) );
if ( array_diff( $oldSecondaryGroups, $newSecondaryGroups ) or array_diff( $newSecondaryGroups, $oldSecondaryGroups ) )
{
$member->logHistory( 'core', 'group', array( 'type' => 'secondary', 'by' => 'api', 'apiKey' => $this->apiKey ? $this->apiKey->id : NULL, 'client' => $this->client ? $this->client->client_id : NULL, 'old' => $oldSecondaryGroups, 'new' => $newSecondaryGroups ), $this->member ?: FALSE );
}
return new \IPS\Api\Response( 200, $member->apiOutput() );
return new \IPS\Api\Response( 200, $member->apiOutput( $this->member ) );
}
catch ( \OutOfRangeException $e )
{
@@ -257,6 +370,7 @@ class _members extends \IPS\Api\Controller
* DELETE /core/members/{id}
* Deletes a member
*
* @apiclientonly
* @param int $id ID Number
* @throws 1C292/2 INVALID_ID The member ID does not exist
* @return void
@@ -280,4 +394,238 @@ class _members extends \IPS\Api\Controller
throw new \IPS\Api\Exception( 'INVALID_ID', '1C292/2', 404 );
}
}
/**
* GET /core/members/{id}/follows
* Get list of items a member is following
*
* @param int $id ID Number
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @return \IPS\Api\PaginatedResponse<IPS\core\Followed\Follow>
* @throws 2C292/F NO_PERMISSION The authorized user does not have permission to view the follows
* @throws 2C292/I INVALID_ID The member could not be found
*/
public function GETitem_follows( $id )
{
try
{
/* Load member */
$member = \IPS\Member::load( $id );
if( !$member->member_id )
{
throw new \OutOfRangeException;
}
/* We can only adjust follows for ourself, if we are an authorized member */
if ( $this->member and $member->member_id != $this->member->member_id )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/F', 403 );
}
/* Return */
return new \IPS\Api\PaginatedResponse(
200,
\IPS\Db::i()->select( '*', 'core_follow', array( 'follow_member_id=?', $member->member_id ), "follow_added ASC" ),
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
'IPS\core\Followed\Follow',
\IPS\Db::i()->select( 'COUNT(*)', 'core_follow', array( 'follow_member_id=?', $member->member_id ) )->first(),
$this->member,
isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : NULL
);
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/I', 404 );
}
}
/**
* POST /core/members/{id}/follows
* Store a new follow for the member
*
* @param int $id ID Number
* @reqapiparam string followApp Application of the content to follow
* @reqapiparam string followArea Area of the content to follow
* @reqapiparam int followId ID of the content to follow
* @apiparam bool followAnon Whether or not to follow anonymously
* @apiparam bool followNotify Whether or not to receive notifications
* @apiparam string followType Type of notification to receive (immediate=send a notification immediately, daily=daily notification digest, weekly=weekly notification digest)
* @return \IPS\core\Followed\Follow
* @throws 2C292/G NO_PERMISSION The authorized user does not have permission to view the follows
* @throws 2C292/H INVALID_ID The member could not be found
* @throws 2C292/J INVALID_CONTENT The app, area or content ID could not be found
*/
public function POSTitem_follows( $id )
{
try
{
/* Load member */
$member = \IPS\Member::load( $id );
if( !$member->member_id )
{
throw new \OutOfRangeException;
}
/* We can only adjust follows for ourself, if we are an authorized member */
if ( $this->member and $member->member_id != $this->member->member_id )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/G', 403 );
}
/* Make sure follow app/area/id is valid (Phil I'm looking at you) */
try
{
$classToFollow = 'IPS\\' . \IPS\Request::i()->followApp . '\\' . mb_ucfirst( \IPS\Request::i()->followArea );
if( !class_exists( $classToFollow ) )
{
throw new \OutOfRangeException;
}
$thingToFollow = $classToFollow::load( \IPS\Request::i()->followId );
}
catch( \Exception $e )
{
throw new \IPS\Api\Exception( 'INVALID_CONTENT', '2C292/J', 404 );
}
/* If we are already following this, update instead of insert */
try
{
$follow = \IPS\core\Followed\Follow::load( md5( \IPS\Request::i()->followApp . ';' . \IPS\Request::i()->followArea . ';' . \IPS\Request::i()->followId . ';' . $member->member_id ) );
}
catch( \OutOfRangeException $e )
{
$follow = new \IPS\core\Followed\Follow;
$follow->member_id = $member->member_id;
$follow->app = \IPS\Request::i()->followApp;
$follow->area = \IPS\Request::i()->followArea;
$follow->rel_id = \IPS\Request::i()->followId;
}
$follow->is_anon = ( isset( \IPS\Request::i()->followAnon ) ) ? (int) \IPS\Request::i()->followAnon : 0;
$follow->notify_do = ( isset( \IPS\Request::i()->followType ) AND \IPS\Request::i()->followType == 'none' ) ? 0 : ( ( isset( \IPS\Request::i()->followNotify ) ) ? (int) \IPS\Request::i()->followNotify : 1 );
$follow->notify_freq = ( isset( \IPS\Request::i()->followType ) AND in_array( \IPS\Request::i()->followType, array( 'none', 'immediate', 'daily', 'weekly' ) ) ) ? \IPS\Request::i()->followType : 'immediate';
$follow->save();
/* If we're following a club, follow all nodes in the club automatically */
if( $follow->app == 'core' and $follow->area == 'club' )
{
$thing = \IPS\Member\Club::loadAndCheckPerms( $follow->rel_id );
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 ) );
/* If we are already following this, update instead of insert */
try
{
$nodeFollow = \IPS\core\Followed\Follow::load( md5( $followApp . ';' . $followArea . ';' . $node['node_id'] . ';' . $member->member_id ) );
}
catch( \OutOfRangeException $e )
{
$nodeFollow = new \IPS\core\Followed\Follow;
$nodeFollow->member_id = $member->member_id;
$nodeFollow->app = $followApp;
$nodeFollow->area = $followArea;
$nodeFollow->rel_id = $node['node_id'];
}
$nodeFollow->is_anon = $follow->is_anon;
$nodeFollow->notify_do = $follow->notify_do;
$nodeFollow->notify_freq = $follow->notify_freq;
$nodeFollow->save();
}
}
return new \IPS\Api\Response( 200, $follow->apiOutput( $this->member ) );
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/H', 404 );
}
}
/**
* DELETE /core/members/{id}/follows
* Delete a follow for the member
*
* @param int $id ID Number
* @apiparam string followKey Follow Key
* @throws 2C292/C INVALID_ID The member could not be found
* @throws 2C292/E INVALID_FOLLOW_KEY The follow does not exist or does not belong to this member
* @throws 2C292/D NO_PERMISSION The authorized user does not have permission to delete the follow
* @return void
*/
public function DELETEitem_follows( $id )
{
try
{
/* Load member */
$member = \IPS\Member::load( $id );
if( !$member->member_id )
{
throw new \OutOfRangeException;
}
/* We can only adjust follows for ourself, if we are an authorized member */
if ( $this->member and $member->member_id != $this->member->member_id )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2C292/D', 403 );
}
/* Load our follow, and make sure it belongs to the specified member */
try
{
if( !isset( \IPS\Request::i()->followKey ) )
{
throw new \UnderflowException;
}
$follow = \IPS\Db::i()->select( '*', 'core_follow', array( 'follow_id=?', \IPS\Request::i()->followKey ) )->first();
if( $follow['follow_member_id'] != $member->member_id )
{
throw new \UnderflowException;
}
}
catch( \UnderflowException $e )
{
throw new \IPS\Api\Exception( 'INVALID_FOLLOW_KEY', '2C292/E', 404 );
}
/* Unfollow */
\IPS\Db::i()->delete( 'core_follow', array( 'follow_id=?', \IPS\Request::i()->followKey ) );
/* If this is a club, unfollow all nodes in the club too */
if( $follow['follow_app'] == 'core' AND $follow['follow_area'] == 'club' )
{
$class = 'IPS\Member\Club';
try
{
$thing = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
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'] . ';' . $member->member_id ), $member->member_id ) );
}
}
catch ( \OutOfRangeException $e ){}
}
return new \IPS\Api\Response( 200, NULL );
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '2C292/C', 404 );
}
}
}
+165
View File
@@ -0,0 +1,165 @@
<?php
/**
* @brief Personal Conversations API
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 1 Dec 2017
* @note We intentionally have not added any way to fetch messages to match the built in privacy functionality
*/
namespace IPS\core\api;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Personal Conversations API
*/
class _messages extends \IPS\Api\Controller
{
/**
* POST /core/messages
* Create a new personal conversation
*
* @apiclientonly
* @apiparam int from User ID conversation is from
* @apiparam array to One or more user IDs conversation is sent to
* @apiparam string title Conversation title
* @apiparam string body Conversation body
* @throws 1C374/2 INVALID_SENDER Sender was not supplied or is invalid
* @throws 1C374/3 INVALID_RECIPIENT No recipients were supplied
* @throws 1C374/4 INVALID_RECIPIENT One or more recipients are invalid
* @throws 1C374/5 MISSING_TITLE_OR_BODY The title and/or body of the conversation were not supplied
* @return int Conversation ID
*/
public function POSTindex()
{
/* Make sure there is a valid sender */
if ( !isset( \IPS\Request::i()->from ) OR !\IPS\Member::load( (int) \IPS\Request::i()->from )->member_id )
{
throw new \IPS\Api\Exception( 'INVALID_SENDER', '1C374/2', 404 );
}
/* Verify there are recipients and all the recipients are valid */
if( !isset( \IPS\Request::i()->to ) OR !is_array( \IPS\Request::i()->to ) OR !count( \IPS\Request::i()->to ) )
{
throw new \IPS\Api\Exception( 'INVALID_RECIPIENT', '1C374/3', 404 );
}
else
{
foreach( \IPS\Request::i()->to as $to )
{
if( !\IPS\Member::load( (int) $to )->member_id )
{
throw new \IPS\Api\Exception( 'INVALID_RECIPIENT', '1C374/4', 404 );
}
}
}
/* Make sure we have a title and body */
if( !isset( \IPS\Request::i()->title ) OR !isset( \IPS\Request::i()->body ) )
{
throw new \IPS\Api\Exception( 'MISSING_TITLE_OR_BODY', '1C374/5', 404 );
}
/* Create the conversation */
$item = \IPS\core\Messenger\Conversation::createItem( \IPS\Member::load( (int) \IPS\Request::i()->from ), \IPS\Request::i()->ipAddress(), \IPS\DateTime::create(), NULL );
$item->title = \IPS\Request::i()->title;
$item->to_count = count( \IPS\Request::i()->to );
$item->save();
/* Create the first message */
$postContents = \IPS\Text\Parser::parseStatic( \IPS\Request::i()->body, TRUE, NULL, \IPS\Member::load( (int) \IPS\Request::i()->from ), 'core_Messaging' );
$commentClass = $item::$commentClass;
$post = $commentClass::create( $item, $postContents, TRUE, NULL, NULL, \IPS\Member::load( (int) \IPS\Request::i()->from ), \IPS\DateTime::create() );
$item->first_msg_id = $post->id;
$item->save();
/* Authorize sender and recipients */
$item->authorize( array_map( function( $member ) { return (int) $member; }, array_merge( array( \IPS\Request::i()->from ), \IPS\Request::i()->to ) ) );
/* Send notifications */
$post->sendNotifications();
return new \IPS\Api\Response( 201, $item->id );
}
/**
* POST /core/messages/{id}
* Add a reply to a personal conversation
*
* @apiclientonly
* @apiparam string body Message body
* @apiparam int from Person responding to message (must be part of conversation)
* @param int $id ID Number
* @throws 1C374/6 INVALID_ID The personal conversation ID does not exist
* @throws 1C374/7 INVALID_SENDER The sender ID supplied was not valid
* @throws 1C374/8 SENDER_NO_PERMISSON The sender supplied does not have permmission to reply to the conversation
* @return bool
*/
public function POSTitem( $id )
{
try
{
$message = \IPS\core\Messenger\Conversation::load( $id );
/* Make sure we have a member, and the member is authorized to reply */
if( !isset( \IPS\Request::i()->from ) OR !\IPS\Member::load( (int) \IPS\Request::i()->from )->member_id )
{
throw new \IPS\Api\Exception( 'INVALID_SENDER', '1C374/7', 404 );
}
if( !$message->canView( \IPS\Member::load( (int) \IPS\Request::i()->from ) ) )
{
throw new \IPS\Api\Exception( 'SENDER_NO_PERMISSON', '1C374/8', 403 );
}
/* Create the reply */
$postContents = \IPS\Text\Parser::parseStatic( \IPS\Request::i()->body, TRUE, NULL, \IPS\Member::load( (int) \IPS\Request::i()->from ), 'core_Messaging' );
$commentClass = $message::$commentClass;
$post = $commentClass::create( $message, $postContents, TRUE, NULL, NULL, \IPS\Member::load( (int) \IPS\Request::i()->from ), \IPS\DateTime::create() );
/* Send notifications */
$post->sendNotifications();
return new \IPS\Api\Response( 200, TRUE );
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '1C374/6', 404 );
}
}
/**
* DELETE /core/messages/{id}
* Deletes a personal conversation
*
* @apiclientonly
* @param int $id ID Number
* @throws 1C292/2 INVALID_ID The personal conversation ID does not exist
* @return void
*/
public function DELETEitem( $id )
{
try
{
$message = \IPS\core\Messenger\Conversation::load( $id );
$message->delete();
return new \IPS\Api\Response( 200, NULL );
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '1C374/1', 404 );
}
}
}
+212
View File
@@ -0,0 +1,212 @@
<?php
/**
* @brief Search API
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 17 Oct 2017
*/
namespace IPS\core\api;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Search API
*/
class _search extends \IPS\Api\Controller
{
/**
* GET /core/search
* Perform a search and get a list of results
*
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @apiparam string q String to search for
* @apiparam string tags Comma-separated list of tags to search for
* @apiparam string type Content type class to restrict searches to
* @apiparam int item Restrict searches to comments or reviews made to the specified item
* @apiparam string nodes Comma-separated list of node IDs to restrict searches to
* @apiparam int search_min_comments Minimum number of comments search results must have for content types that support comments
* @apiparam int search_min_replies Minimum number of comments search results must have for content types that require the first comment (e.g. topics)
* @apiparam int search_min_reviews Minimum number of reviews search results must have
* @apiparam int search_min_views Minimum number of views search results must have
* @apiparam string author Restrict searches to results posted by this member (name)
* @apiparam string club Comma-separated list of club IDs to restrict searches to
* @apiparam string start_before Date period (from current time) that search results should start before
* @apiparam string start_after Date period (from current time) that search results should start after
* @apiparam string updated_before Date period (from current time) that search results should last be updated before
* @apiparam string updated_after Date period (from current time) that search results should last be updated after
* @apiparam string sortby Sort by method (newest or relevancy)
* @apiparam string eitherTermsOrTags Whether to search both tags and search term ("and") or either ("or")
* @apiparam string search_and_or Whether to perform an "and" search or an "or" search for all search terms
* @apiparam string search_in Specify "titles" to search in titles only, otherwise titles and content are both searched
* @return array
* @apiresponse int page api_int_page
* @apiresponse int perPage api_int_perpage
* @apiresponse int totalResults api_int_totalresults
* @apiresponse int totalPages api_int_totalpages
* @apiresponse [\IPS\Content\Search\Result] results api_results_thispage
*/
public function GETindex()
{
/* Get valid content types */
$contentTypes = \IPS\core\modules\front\search\search::contentTypes();
/* Initialize search */
$query = \IPS\Content\Search\Query::init();
/* Set content type */
if ( isset( \IPS\Request::i()->type ) and array_key_exists( \IPS\Request::i()->type, $contentTypes ) )
{
if ( isset( \IPS\Request::i()->item ) )
{
$class = $contentTypes[ \IPS\Request::i()->type ];
try
{
$item = $class::loadAndCheckPerms( \IPS\Request::i()->item );
$query->filterByContent( array( \IPS\Content\Search\ContentFilter::init( $class )->onlyInItems( array( \IPS\Request::i()->item ) ) ) );
}
catch ( \OutOfRangeException $e ) { }
}
else
{
$filter = \IPS\Content\Search\ContentFilter::init( $contentTypes[ \IPS\Request::i()->type ] );
if ( isset( \IPS\Request::i()->nodes ) )
{
$filter->onlyInContainers( explode( ',', \IPS\Request::i()->nodes ) );
}
if ( isset( \IPS\Request::i()->search_min_comments ) )
{
$filter->minimumComments( \IPS\Request::i()->search_min_comments );
}
if ( isset( \IPS\Request::i()->search_min_replies ) )
{
$filter->minimumComments( \IPS\Request::i()->search_min_replies + 1 );
}
if ( isset( \IPS\Request::i()->search_min_reviews ) )
{
$filter->minimumReviews( \IPS\Request::i()->search_min_reviews );
}
if ( isset( \IPS\Request::i()->search_min_views ) )
{
$filter->minimumViews( \IPS\Request::i()->search_min_views );
}
$query->filterByContent( array( $filter ) );
}
}
/* Filter by author */
if ( isset( \IPS\Request::i()->author ) )
{
$author = \IPS\Member::load( \IPS\Request::i()->author, 'name' );
if ( $author->member_id )
{
$query->filterByAuthor( $author );
}
}
/* Filter by club */
if ( isset( \IPS\Request::i()->club ) AND \IPS\Settings::i()->clubs )
{
$query->filterByClub( explode( ',', \IPS\Request::i()->club ) );
}
/* Set time cutoffs */
foreach ( array( 'start' => 'filterByCreateDate', 'updated' => 'filterByLastUpdatedDate' ) as $k => $method )
{
$beforeKey = "{$k}_before";
$afterKey = "{$k}_after";
if ( isset( \IPS\Request::i()->$beforeKey ) or isset( \IPS\Request::i()->$afterKey ) )
{
foreach ( array( 'before', 'after' ) as $l )
{
$$l = NULL;
$key = "{$l}Key";
if ( isset( \IPS\Request::i()->$$key ) AND \IPS\Request::i()->$$key != 'any' )
{
switch ( \IPS\Request::i()->$$key )
{
case 'day':
$$l = \IPS\DateTime::create()->sub( new \DateInterval( 'P1D' ) );
break;
case 'week':
$$l = \IPS\DateTime::create()->sub( new \DateInterval( 'P1W' ) );
break;
case 'month':
$$l = \IPS\DateTime::create()->sub( new \DateInterval( 'P1M' ) );
break;
case 'six_months':
$$l = \IPS\DateTime::create()->sub( new \DateInterval( 'P6M' ) );
break;
case 'year':
$$l = \IPS\DateTime::create()->sub( new \DateInterval( 'P1Y' ) );
break;
default:
$$l = \IPS\DateTime::ts( \IPS\Request::i()->$$key );
break;
}
}
}
$query->$method( $after, $before );
}
}
/* Set Order */
if ( ! isset( \IPS\Request::i()->sortby ) )
{
\IPS\Request::i()->sortby = $query->getDefaultSortMethod();
}
switch( \IPS\Request::i()->sortby )
{
case 'newest':
$query->setOrder( \IPS\Content\Search\Query::ORDER_NEWEST_CREATED );
break;
case 'relevancy':
$query->setOrder( \IPS\Content\Search\Query::ORDER_RELEVANCY );
break;
}
$flags = ( isset( \IPS\Request::i()->eitherTermsOrTags ) and \IPS\Request::i()->eitherTermsOrTags === 'and' ) ? \IPS\Content\Search\Query::TERM_AND_TAGS : \IPS\Content\Search\Query::TERM_OR_TAGS;
$operator = NULL;
if ( isset( \IPS\Request::i()->search_and_or ) and in_array( \IPS\Request::i()->search_and_or, array( \IPS\Content\Search\Query::OPERATOR_OR, \IPS\Content\Search\Query::OPERATOR_AND ) ) )
{
$operator = \IPS\Request::i()->search_and_or;
}
if ( isset( \IPS\Request::i()->search_in ) and \IPS\Request::i()->search_in === 'titles' )
{
$flags = $flags | \IPS\Content\Search\Query::TERM_TITLES_ONLY;
}
/* Return */
return new \IPS\Content\Search\ApiResponse(
200,
array( $query, $flags, isset( \IPS\Request::i()->q ) ? ( \IPS\Request::i()->q ) : NULL, isset( \IPS\Request::i()->tags ) ? explode( ',', \IPS\Request::i()->tags ) : NULL, $operator ),
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
NULL,
0,
$this->member,
isset( \IPS\Request::i()->perPage ) ? \IPS\Request::i()->perPage : NULL
);
}
}