mirror of
https://github.com/salesagility/SuiteCRM.git
synced 2025-03-14 21:42:52 +00:00
new api init
This commit is contained in:
parent
a9d6169b7f
commit
a29cc3a54b
56 changed files with 2344 additions and 3 deletions
Api
.htaccess
composer.jsonCore
V8
BeanManager.php
index.phpConfig
Controller
BaseController.php
InvocationStrategy
LogoutController.phpModuleController.phpRelationshipController.phpDictionary
Factory
Helper
JsonApi
Helper
Response
Middleware
OAuth2
Param
Service
modules/OAuth2Clients
7
Api/.htaccess
Normal file
7
Api/.htaccess
Normal file
|
@ -0,0 +1,7 @@
|
|||
Require all granted
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !Api/index.php$
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L,QSA]
|
10
Api/Core/Config/slim.php
Normal file
10
Api/Core/Config/slim.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'settings' => [
|
||||
/** Additional information about exceptions are displayed by the default error handler. */
|
||||
'displayErrorDetails' => true,
|
||||
/** Routes are accessible in middleware. */
|
||||
'determineRouteBeforeAppMiddleware' => true,
|
||||
]
|
||||
];
|
50
Api/Core/Loader/ContainerLoader.php
Normal file
50
Api/Core/Loader/ContainerLoader.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
namespace Api\Core\Loader;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class ContainerLoader
|
||||
{
|
||||
/**
|
||||
* Load all service containers
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public static function configure(ContainerInterface $container)
|
||||
{
|
||||
$containerConfig = [
|
||||
__DIR__ . '/../../V8/Config/services.php'
|
||||
];
|
||||
|
||||
$services = self::loadFiles($containerConfig);
|
||||
foreach ($services as $service => $closure) {
|
||||
$container[$service] = $closure;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
*
|
||||
* @return array
|
||||
* @throws \RuntimeException When config file is not readable or does not contain an array.
|
||||
*/
|
||||
private static function loadFiles(array $files)
|
||||
{
|
||||
$configs = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (!file_exists($file) || !is_readable($file)) {
|
||||
throw new \RuntimeException(sprintf('Config file %s is not readable', $file));
|
||||
}
|
||||
|
||||
$config = require $file;
|
||||
if (!is_array($config)) {
|
||||
throw new \RuntimeException(sprintf('Config file %s is invalid', $file));
|
||||
}
|
||||
|
||||
$configs[] = $config;
|
||||
}
|
||||
|
||||
return !$configs ? $configs : array_merge(...$configs);
|
||||
}
|
||||
}
|
17
Api/Core/Loader/RouteLoader.php
Normal file
17
Api/Core/Loader/RouteLoader.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace Api\Core\Loader;
|
||||
|
||||
use Slim\App;
|
||||
|
||||
class RouteLoader
|
||||
{
|
||||
/**
|
||||
* Load all app routes
|
||||
*
|
||||
* @param App $app
|
||||
*/
|
||||
public static function configureRoutes(App $app)
|
||||
{
|
||||
require __DIR__ . '/../../V8/Config/routes.php';
|
||||
}
|
||||
}
|
16
Api/Core/app.php
Normal file
16
Api/Core/app.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
// @codingStandardsIgnoreStart
|
||||
if (!defined('sugarEntry')) {
|
||||
define('sugarEntry', true);
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
chdir(__DIR__ . '/../../');
|
||||
require_once __DIR__ . '/../../include/entryPoint.php';
|
||||
|
||||
$slimSettings = require __DIR__ . '/Config/slim.php';
|
||||
$container = new \Slim\Container($slimSettings);
|
||||
\Api\Core\Loader\ContainerLoader::configure($container);
|
||||
$app = new \Slim\App($container);
|
||||
|
||||
\Api\Core\Loader\RouteLoader::configureRoutes($app);
|
106
Api/V8/BeanManager.php
Normal file
106
Api/V8/BeanManager.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
namespace Api\V8;
|
||||
|
||||
class BeanManager
|
||||
{
|
||||
const MAX_RECORDS_PER_PAGE = 20;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $beanAliases;
|
||||
|
||||
/**
|
||||
* @param array $beanAliases
|
||||
*/
|
||||
public function __construct(array $beanAliases)
|
||||
{
|
||||
$this->beanAliases = $beanAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module
|
||||
*
|
||||
* @return \SugarBean
|
||||
* @throws \InvalidArgumentException When the module is invalid
|
||||
*/
|
||||
public function newBeanSafe($module)
|
||||
{
|
||||
$bean = \BeanFactory::newBean($module);
|
||||
|
||||
if (!$bean instanceof \SugarBean) {
|
||||
throw new \InvalidArgumentException(sprintf('Module %s does not exist', $module));
|
||||
}
|
||||
|
||||
return $bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module
|
||||
* @param string|null $id
|
||||
* @param array $params
|
||||
* @param boolean $deleted
|
||||
*
|
||||
* @return \SugarBean|boolean
|
||||
*/
|
||||
public function getBean($module, $id = null, array $params = [], $deleted = true)
|
||||
{
|
||||
return \BeanFactory::getBean($module, $id, $params, $deleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module
|
||||
* @param string $id
|
||||
* @param array $params
|
||||
* @param bool $deleted
|
||||
*
|
||||
* @return \SugarBean
|
||||
* @throws \DomainException When bean id is empty or bean is not found by name.
|
||||
* @throws \InvalidArgumentException When bean is not found with the given id.
|
||||
*/
|
||||
public function getBeanSafe(
|
||||
$module,
|
||||
$id,
|
||||
array $params = [],
|
||||
$deleted = true
|
||||
) {
|
||||
if (empty($id)) {
|
||||
throw new \DomainException('Module id is empty when trying to fetch ' . $module);
|
||||
}
|
||||
|
||||
$objectName = \BeanFactory::getObjectName($module);
|
||||
if (!$objectName && array_key_exists($module, $this->beanAliases)) {
|
||||
$objectName = \BeanFactory::getObjectName($this->beanAliases[$module]);
|
||||
$module = $this->beanAliases[$module];
|
||||
}
|
||||
|
||||
if (!$objectName) {
|
||||
throw new \DomainException(sprintf('Module with name %s is not found', $module));
|
||||
}
|
||||
|
||||
$bean = $this->getBean($module, $id, $params, $deleted);
|
||||
if ($bean === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('%s module with id %s is not found', $module, $id)
|
||||
);
|
||||
}
|
||||
|
||||
return $bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $module
|
||||
*
|
||||
* @return \SugarBean
|
||||
*/
|
||||
public function findBean($module)
|
||||
{
|
||||
if (array_key_exists($module, $this->beanAliases)) {
|
||||
$module = $this->beanAliases[$module];
|
||||
}
|
||||
|
||||
$bean = $this->newBeanSafe($module);
|
||||
|
||||
return $bean;
|
||||
}
|
||||
}
|
44
Api/V8/Config/routes.php
Normal file
44
Api/V8/Config/routes.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\Controller\LogoutController;
|
||||
use Api\V8\Controller\ModuleController;
|
||||
use Api\V8\Controller\RelationshipController;
|
||||
use Api\V8\Factory\ParamsMiddlewareFactory;
|
||||
use Api\V8\Param\ModuleParams;
|
||||
use Api\V8\Param\RelationshipParams;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\Middleware\AuthorizationServerMiddleware;
|
||||
use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
|
||||
use League\OAuth2\Server\ResourceServer;
|
||||
|
||||
$app->group('', function () use ($app) {
|
||||
/**
|
||||
* OAuth2 access token
|
||||
*/
|
||||
$app->post('/access_token', function () {
|
||||
})->add(new AuthorizationServerMiddleware($app->getContainer()->get(AuthorizationServer::class)));
|
||||
|
||||
$app->group('/V8', function () use ($app) {
|
||||
/** @var ParamsMiddlewareFactory $paramsMiddlewareFactory */
|
||||
$paramsMiddlewareFactory = $app->getContainer()->get(ParamsMiddlewareFactory::class);
|
||||
|
||||
$this->post('/logout', LogoutController::LOGOUT);
|
||||
|
||||
$this
|
||||
->get('/module/{moduleName}', ModuleController::GET_MODULE_RECORDS)
|
||||
->add($paramsMiddlewareFactory->bind(ModuleParams::class));
|
||||
|
||||
$this
|
||||
->get('/module/{moduleName}/{id}', ModuleController::GET_MODULE_RECORD)
|
||||
->add($paramsMiddlewareFactory->bind(ModuleParams::class));
|
||||
|
||||
$this
|
||||
->post('/module/{moduleName}', ModuleController::CREATE_MODULE_RECORD)
|
||||
->add($paramsMiddlewareFactory->bind(ModuleParams::class));
|
||||
|
||||
$this
|
||||
->get('/relationship/{moduleName}', RelationshipController::GET_RELATIONSHIP)
|
||||
->add($paramsMiddlewareFactory->bind(RelationshipParams::class));
|
||||
|
||||
})->add(new ResourceServerMiddleware($app->getContainer()->get(ResourceServer::class)));
|
||||
});
|
27
Api/V8/Config/services.php
Normal file
27
Api/V8/Config/services.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\Controller\InvocationStrategy\SuiteInvocationStrategy;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
|
||||
return
|
||||
[
|
||||
/** overwrite slim's foundHandler */
|
||||
'foundHandler' => function () {
|
||||
return new SuiteInvocationStrategy();
|
||||
},
|
||||
BeanManager::class => function (Container $container) {
|
||||
return new BeanManager(
|
||||
$container->get('beanAliases')
|
||||
);
|
||||
},
|
||||
] +
|
||||
(require __DIR__ . '/services/beanAliases.php') +
|
||||
(require __DIR__ . '/services/controllers.php') +
|
||||
(require __DIR__ . '/services/factories.php') +
|
||||
(require __DIR__ . '/services/globals.php') +
|
||||
(require __DIR__ . '/services/helpers.php') +
|
||||
(require __DIR__ . '/services/middlewares.php') +
|
||||
(require __DIR__ . '/services/params.php') +
|
||||
(require __DIR__ . '/services/services.php') +
|
||||
(require __DIR__ . '/services/validators.php');
|
11
Api/V8/Config/services/beanAliases.php
Normal file
11
Api/V8/Config/services/beanAliases.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'beanAliases' => function () {
|
||||
return [
|
||||
'Account' => 'Accounts',
|
||||
'account' => 'Accounts',
|
||||
'accounts' => 'Accounts',
|
||||
];
|
||||
}
|
||||
];
|
26
Api/V8/Config/services/controllers.php
Normal file
26
Api/V8/Config/services/controllers.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\Controller;
|
||||
use Api\V8\Service\LogoutService;
|
||||
use Api\V8\Service\ModuleService;
|
||||
use Api\V8\Service\RelationshipService;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
Controller\ModuleController::class => function (Container $container) {
|
||||
return new Controller\ModuleController(
|
||||
$container->get(ModuleService::class)
|
||||
);
|
||||
},
|
||||
Controller\LogoutController::class => function (Container $container) {
|
||||
return new Controller\LogoutController(
|
||||
$container->get(LogoutService::class),
|
||||
$container->get(\League\OAuth2\Server\ResourceServer::class)
|
||||
);
|
||||
},
|
||||
Controller\RelationshipController::class => function (Container $container) {
|
||||
return new Controller\RelationshipController(
|
||||
$container->get(RelationshipService::class)
|
||||
);
|
||||
},
|
||||
];
|
13
Api/V8/Config/services/factories.php
Normal file
13
Api/V8/Config/services/factories.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\Factory;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
Factory\ParamsMiddlewareFactory::class => function (Container $container) {
|
||||
return new Factory\ParamsMiddlewareFactory($container);
|
||||
},
|
||||
Factory\ValidatorFactory::class => function (Container $container) {
|
||||
return new Factory\ValidatorFactory($container->get('Validation'));
|
||||
},
|
||||
];
|
8
Api/V8/Config/services/globals.php
Normal file
8
Api/V8/Config/services/globals.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'suiteConfig' => function () {
|
||||
global $sugar_config;
|
||||
return $sugar_config;
|
||||
},
|
||||
];
|
22
Api/V8/Config/services/helpers.php
Normal file
22
Api/V8/Config/services/helpers.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\Helper;
|
||||
use Api\V8\JsonApi\Helper as ApiHelper;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
Helper\VarDefHelper::class => function () {
|
||||
return new Helper\VarDefHelper();
|
||||
},
|
||||
ApiHelper\AttributeObjectHelper::class => function (Container $container) {
|
||||
return new ApiHelper\AttributeObjectHelper(
|
||||
$container->get(BeanManager::class)
|
||||
);
|
||||
},
|
||||
ApiHelper\RelationshipObjectHelper::class => function (Container $container) {
|
||||
return new ApiHelper\RelationshipObjectHelper(
|
||||
$container->get(Helper\VarDefHelper::class)
|
||||
);
|
||||
},
|
||||
];
|
59
Api/V8/Config/services/middlewares.php
Normal file
59
Api/V8/Config/services/middlewares.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\OAuth2\Entity\AccessTokenEntity;
|
||||
use Api\V8\OAuth2\Entity\ClientEntity;
|
||||
use Api\V8\OAuth2\Repository\AccessTokenRepository;
|
||||
use Api\V8\OAuth2\Repository\ClientRepository;
|
||||
use Api\V8\OAuth2\Repository\RefreshTokenRepository;
|
||||
use Api\V8\OAuth2\Repository\ScopeRepository;
|
||||
use Api\V8\OAuth2\Repository\UserRepository;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\Grant\PasswordGrant;
|
||||
use League\OAuth2\Server\ResourceServer;
|
||||
|
||||
return [
|
||||
AuthorizationServer::class => function (Container $container) {
|
||||
$server = new AuthorizationServer(
|
||||
new ClientRepository(
|
||||
new ClientEntity(),
|
||||
$container->get(BeanManager::class)
|
||||
),
|
||||
new AccessTokenRepository(
|
||||
new AccessTokenEntity(),
|
||||
$container->get(BeanManager::class)
|
||||
),
|
||||
new ScopeRepository(),
|
||||
'file://' . __DIR__ . '/../../OAuth2/private.key',
|
||||
'file://' . __DIR__ . '/../../OAuth2/public.key'
|
||||
);
|
||||
$server->setEncryptionKey('KcWedk/XtvWgtuf7UHx6ayHnrIaMC/t4RjZrdVBY2Ho=');
|
||||
|
||||
// Client credentials grant
|
||||
$server->enableGrantType(
|
||||
new \League\OAuth2\Server\Grant\ClientCredentialsGrant(),
|
||||
new \DateInterval('PT12H')
|
||||
);
|
||||
|
||||
// Password credentials grant
|
||||
$server->enableGrantType(
|
||||
new PasswordGrant(
|
||||
new UserRepository($container->get(BeanManager::class)),
|
||||
new RefreshTokenRepository($container->get(BeanManager::class))
|
||||
),
|
||||
new \DateInterval('PT12H')
|
||||
);
|
||||
|
||||
return $server;
|
||||
},
|
||||
ResourceServer::class => function (Container $container) {
|
||||
return new ResourceServer(
|
||||
new AccessTokenRepository(
|
||||
new AccessTokenEntity(),
|
||||
$container->get(BeanManager::class)
|
||||
),
|
||||
'file://' . __DIR__ . '/../../OAuth2/public.key'
|
||||
);
|
||||
},
|
||||
];
|
11
Api/V8/Config/services/params.php
Normal file
11
Api/V8/Config/services/params.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\Factory\ValidatorFactory;
|
||||
use Api\V8\Param;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
Param\ModuleParams::class => function (Container $container) {
|
||||
return new Param\ModuleParams($container->get(ValidatorFactory::class));
|
||||
}
|
||||
];
|
27
Api/V8/Config/services/services.php
Normal file
27
Api/V8/Config/services/services.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\JsonApi\Helper\AttributeObjectHelper;
|
||||
use Api\V8\JsonApi\Helper\RelationshipObjectHelper;
|
||||
use Api\V8\Service;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
Service\ModuleService::class => function (Container $container) {
|
||||
return new Service\ModuleService(
|
||||
$container->get(BeanManager::class),
|
||||
$container->get(AttributeObjectHelper::class),
|
||||
$container->get(RelationshipObjectHelper::class)
|
||||
);
|
||||
},
|
||||
Service\LogoutService::class => function (Container $container) {
|
||||
return new Service\LogoutService(
|
||||
$container->get(BeanManager::class)
|
||||
);
|
||||
},
|
||||
Service\RelationshipService::class => function (Container $container) {
|
||||
return new Service\RelationshipService(
|
||||
$container->get(BeanManager::class)
|
||||
);
|
||||
},
|
||||
];
|
9
Api/V8/Config/services/validators.php
Normal file
9
Api/V8/Config/services/validators.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
use Symfony\Component\Validator\ValidatorBuilder;
|
||||
|
||||
return [
|
||||
'Validation' => function () {
|
||||
return (new ValidatorBuilder())->getValidator();
|
||||
},
|
||||
];
|
51
Api/V8/Controller/BaseController.php
Normal file
51
Api/V8/Controller/BaseController.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
namespace Api\V8\Controller;
|
||||
|
||||
use Api\V8\JsonApi\Response\DocumentResponse;
|
||||
use Api\V8\JsonApi\Response\ErrorResponse;
|
||||
use Slim\Http\Response as HttpResponse;
|
||||
|
||||
abstract class BaseController
|
||||
{
|
||||
const MEDIA_TYPE = 'application/vnd.api+json';
|
||||
|
||||
/**
|
||||
* @param HttpResponse $httpResponse
|
||||
* @param DocumentResponse|ErrorResponse $response
|
||||
* @param int $status
|
||||
*
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function generateResponse(
|
||||
HttpResponse $httpResponse,
|
||||
DocumentResponse $response,
|
||||
$status
|
||||
) {
|
||||
return $httpResponse
|
||||
->withStatus($status)
|
||||
->withHeader('Accept', static::MEDIA_TYPE)
|
||||
->withHeader('Content-type', static::MEDIA_TYPE)
|
||||
->write(
|
||||
json_encode(
|
||||
$response,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HttpResponse $httpResponse
|
||||
* @param \Exception $exception
|
||||
* @param int $status
|
||||
*
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function generateErrorResponse(HttpResponse $httpResponse, $exception, $status)
|
||||
{
|
||||
$response = new ErrorResponse();
|
||||
$response->setStatus($status);
|
||||
$response->setDetail($exception->getMessage());
|
||||
|
||||
return $this->generateResponse($httpResponse, $response, $status);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
namespace Api\V8\Controller\InvocationStrategy;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Slim\Interfaces\InvocationStrategyInterface;
|
||||
|
||||
class SuiteInvocationStrategy implements InvocationStrategyInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __invoke(
|
||||
callable $callable,
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
array $routeArguments
|
||||
) {
|
||||
foreach ($routeArguments as $attribute => $value) {
|
||||
$request = $request->withAttribute($attribute, $value);
|
||||
}
|
||||
|
||||
$controllerArgs = [$request, $response, $routeArguments];
|
||||
|
||||
if ($request->getAttribute('params')) {
|
||||
$controllerArgs[] = $request->getAttribute('params');
|
||||
}
|
||||
|
||||
return $callable(...$controllerArgs);
|
||||
}
|
||||
}
|
54
Api/V8/Controller/LogoutController.php
Normal file
54
Api/V8/Controller/LogoutController.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
namespace Api\V8\Controller;
|
||||
|
||||
use Api\V8\Service\LogoutService;
|
||||
use League\OAuth2\Server\ResourceServer;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class LogoutController extends BaseController
|
||||
{
|
||||
const LOGOUT = self::class . ':logout';
|
||||
|
||||
/**
|
||||
* @var LogoutService
|
||||
*/
|
||||
private $logoutService;
|
||||
|
||||
/**
|
||||
* @var ResourceServer
|
||||
*/
|
||||
private $resourceServer;
|
||||
|
||||
/**
|
||||
* @param LogoutService $logoutService
|
||||
* @param ResourceServer $resourceServer
|
||||
*/
|
||||
public function __construct(LogoutService $logoutService, ResourceServer $resourceServer)
|
||||
{
|
||||
$this->logoutService = $logoutService;
|
||||
$this->resourceServer = $resourceServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function logout(Request $request, Response $response, array $args)
|
||||
{
|
||||
try {
|
||||
$accessToken = $this->resourceServer
|
||||
->validateAuthenticatedRequest($request)
|
||||
->getAttribute('oauth_access_token_id');
|
||||
|
||||
$logoutResponse = $this->logoutService->logout($accessToken);
|
||||
|
||||
return $this->generateResponse($response, $logoutResponse, 200);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->generateErrorResponse($response, $exception, 400);
|
||||
}
|
||||
}
|
||||
}
|
65
Api/V8/Controller/ModuleController.php
Normal file
65
Api/V8/Controller/ModuleController.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
namespace Api\V8\Controller;
|
||||
|
||||
use Api\V8\Param\ModuleParams;
|
||||
use Api\V8\Service\ModuleService;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class ModuleController extends BaseController
|
||||
{
|
||||
const GET_MODULE_RECORD = self::class . ':getModuleRecord';
|
||||
const GET_MODULE_RECORDS = self::class . ':getModuleRecords';
|
||||
const CREATE_MODULE_RECORD = self::class . ':createModuleRecord';
|
||||
|
||||
/**
|
||||
* @var ModuleService
|
||||
*/
|
||||
private $moduleService;
|
||||
|
||||
/**
|
||||
* @param ModuleService $moduleService
|
||||
*/
|
||||
public function __construct(ModuleService $moduleService)
|
||||
{
|
||||
$this->moduleService = $moduleService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @param ModuleParams $params
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getModuleRecord(Request $request, Response $response, array $args, ModuleParams $params)
|
||||
{
|
||||
try {
|
||||
$jsonResponse = $this->moduleService->getRecord($params, $request->getUri());
|
||||
|
||||
return $this->generateResponse($response, $jsonResponse, 200);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->generateErrorResponse($response, $exception, 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param array $args
|
||||
* @param ModuleParams $params
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getModuleRecords(Request $request, Response $response, array $args, ModuleParams $params)
|
||||
{
|
||||
try {
|
||||
$moduleResponse = $this->moduleService->getRecords($params, $request);
|
||||
|
||||
return $this->generateResponse($response, $moduleResponse, 200);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->generateErrorResponse($response, $exception, 400);
|
||||
}
|
||||
}
|
||||
}
|
27
Api/V8/Controller/RelationshipController.php
Normal file
27
Api/V8/Controller/RelationshipController.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace Api\V8\Controller;
|
||||
|
||||
use Api\V8\Service\RelationshipService;
|
||||
|
||||
class RelationshipController extends BaseController
|
||||
{
|
||||
const GET_RELATIONSHIP = self::class . ':getRelationship';
|
||||
|
||||
/**
|
||||
* @var RelationshipService
|
||||
*/
|
||||
private $relationshipService;
|
||||
|
||||
public function __construct(RelationshipService $relationshipService)
|
||||
{
|
||||
$this->relationshipService = $relationshipService;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getRelationship()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
8
Api/V8/Dictionary/Response.php
Normal file
8
Api/V8/Dictionary/Response.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Api\V8\Dictionary;
|
||||
|
||||
class Response
|
||||
{
|
||||
const MESSAGE_SUCCESS = 'SUCCESS';
|
||||
const MESSAGE_ERROR = 'ERROR';
|
||||
}
|
37
Api/V8/Factory/ParamsMiddlewareFactory.php
Normal file
37
Api/V8/Factory/ParamsMiddlewareFactory.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
namespace Api\V8\Factory;
|
||||
|
||||
use Api\V8\Middleware\ParamsMiddleware;
|
||||
use Interop\Container\ContainerInterface as Container;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class ParamsMiddlewareFactory
|
||||
{
|
||||
/**
|
||||
* @var Container
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $containerId
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public function bind($containerId)
|
||||
{
|
||||
$container = $this->container;
|
||||
|
||||
return function (Request $request, Response $response, callable $next) use ($containerId, $container) {
|
||||
return (new ParamsMiddleware($container->get($containerId)))($request, $response, $next);
|
||||
};
|
||||
}
|
||||
}
|
66
Api/V8/Factory/ValidatorFactory.php
Normal file
66
Api/V8/Factory/ValidatorFactory.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
namespace Api\V8\Factory;
|
||||
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class ValidatorFactory
|
||||
{
|
||||
/**
|
||||
* @var ValidatorInterface
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param ValidatorInterface $validator
|
||||
*/
|
||||
public function __construct(ValidatorInterface $validator)
|
||||
{
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\Validator\Constraint[] $constraints
|
||||
* @param boolean $allowNull
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public function createClosure(array $constraints, $allowNull = false)
|
||||
{
|
||||
return function ($value) use ($constraints, $allowNull) {
|
||||
if ($allowNull && $value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$violations = $this->validator->validate($value, $constraints);
|
||||
|
||||
return !$violations->count();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\Validator\Constraint[] $constraints
|
||||
* @param boolean $allowNull
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public function createClosureForIterator(array $constraints, $allowNull = false)
|
||||
{
|
||||
return function ($value) use ($constraints, $allowNull) {
|
||||
if ($allowNull && $value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_array($value) && !$value instanceof \Iterator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($value as $v) {
|
||||
if ($this->validator->validate($v, $constraints)->count()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
24
Api/V8/Helper/VarDefHelper.php
Normal file
24
Api/V8/Helper/VarDefHelper.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
namespace Api\V8\Helper;
|
||||
|
||||
class VarDefHelper
|
||||
{
|
||||
/**
|
||||
* @param \SugarBean $bean
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllRelationships(\SugarBean $bean)
|
||||
{
|
||||
$relations = [];
|
||||
$linkedFields = $bean->get_linked_fields();
|
||||
|
||||
foreach ($linkedFields as $relation => $varDef) {
|
||||
if (isset($varDef['module']) && $bean->load_relationship($relation)) {
|
||||
$relations[$relation] = $varDef['module'];
|
||||
}
|
||||
}
|
||||
|
||||
return $relations;
|
||||
}
|
||||
}
|
81
Api/V8/JsonApi/Helper/AttributeObjectHelper.php
Normal file
81
Api/V8/JsonApi/Helper/AttributeObjectHelper.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Helper;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
|
||||
class AttributeObjectHelper
|
||||
{
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @param BeanManager $beanManager
|
||||
*/
|
||||
public function __construct(BeanManager $beanManager)
|
||||
{
|
||||
$this->beanManager = $beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SugarBean $bean
|
||||
* @param array|null $fieldParams
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes(\SugarBean $bean, $fieldParams = null)
|
||||
{
|
||||
$bean->fixUpFormatting();
|
||||
$attributes = $bean->toArray();
|
||||
|
||||
// using the ISO 8601 format for dates
|
||||
array_walk($attributes, function (&$value) {
|
||||
if (\DateTime::createFromFormat('Y-m-d H:i:s', $value)) {
|
||||
$value = date(\DateTime::ATOM, strtotime($value));
|
||||
}
|
||||
});
|
||||
|
||||
if ($fieldParams !== null) {
|
||||
$attributes = $this->getFilteredAttributes($fieldParams, $attributes);
|
||||
}
|
||||
|
||||
unset($attributes['id']);
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fieldParams
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException If field(s) is/are not found.
|
||||
*/
|
||||
private function getFilteredAttributes(array $fieldParams, array $attributes)
|
||||
{
|
||||
$module = $this->beanManager->findBean(key($fieldParams));
|
||||
|
||||
// spaces between params are validated in the endpoint's Param class
|
||||
$fields = explode(',', array_shift($fieldParams));
|
||||
$invalidFields = array_filter($fields, function ($field) use ($attributes) {
|
||||
return !array_key_exists($field, $attributes);
|
||||
});
|
||||
|
||||
if ($invalidFields) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'The following field%s in %s module %s not found: %s',
|
||||
count($invalidFields) > 1 ? 's' : '',
|
||||
$module->getObjectName(),
|
||||
count($invalidFields) > 1 ? 'are' : 'is',
|
||||
implode(', ', $invalidFields)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$attributes = array_intersect_key($attributes, array_flip($fields));
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
48
Api/V8/JsonApi/Helper/RelationshipObjectHelper.php
Normal file
48
Api/V8/JsonApi/Helper/RelationshipObjectHelper.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Helper;
|
||||
|
||||
use Api\V8\Helper\VarDefHelper;
|
||||
use Api\V8\JsonApi\Response\LinksResponse;
|
||||
|
||||
class RelationshipObjectHelper
|
||||
{
|
||||
/**
|
||||
* @var VarDefHelper
|
||||
*/
|
||||
private $varDefHelper;
|
||||
|
||||
/**
|
||||
* @param VarDefHelper $varDefHelper
|
||||
*/
|
||||
public function __construct(VarDefHelper $varDefHelper)
|
||||
{
|
||||
$this->varDefHelper = $varDefHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SugarBean $bean
|
||||
* @param string $uriPath
|
||||
* @param string $id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRelationships(\SugarBean $bean, $uriPath, $id)
|
||||
{
|
||||
// if we have module collection, we add id manually to relationship
|
||||
$path = $id === null ? $uriPath . '/' . $bean->id : $uriPath;
|
||||
$relationships = $this->varDefHelper->getAllRelationships($bean);
|
||||
asort($relationships);
|
||||
|
||||
$relationshipsLinks = [];
|
||||
foreach (array_unique($relationships) as $module) {
|
||||
$linkResponse = new LinksResponse();
|
||||
$linkResponse->setRelated(
|
||||
sprintf('/%s/%s/%s', $path, 'relationships', strtolower($module))
|
||||
);
|
||||
|
||||
$relationshipsLinks[$module] = ['links' => $linkResponse];
|
||||
}
|
||||
|
||||
return $relationshipsLinks;
|
||||
}
|
||||
}
|
27
Api/V8/JsonApi/Response/AttributeResponse.php
Normal file
27
Api/V8/JsonApi/Response/AttributeResponse.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class AttributeResponse extends MetaResponse
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @see http://jsonapi.org/format/#document-resource-object-attributes
|
||||
*/
|
||||
private static $forbiddenKeys = ['relationships', 'links'];
|
||||
|
||||
/**
|
||||
* @param array $properties
|
||||
*/
|
||||
public function __construct($properties = [])
|
||||
{
|
||||
parent::__construct($properties);
|
||||
|
||||
$invalidKeys = array_intersect_key($properties, array_flip(self::$forbiddenKeys));
|
||||
if ($invalidKeys) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Attribute object must not contain these keys: ' . implode(', ', array_keys($invalidKeys))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
98
Api/V8/JsonApi/Response/DataResponse.php
Normal file
98
Api/V8/JsonApi/Response/DataResponse.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class DataResponse implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var AttributeResponse
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* @var RelationshipResponse
|
||||
*/
|
||||
private $relationships;
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $id
|
||||
*/
|
||||
public function __construct($type, $id)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AttributeResponse
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttributeResponse $attributes
|
||||
*/
|
||||
public function setAttributes(AttributeResponse $attributes)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RelationshipResponse
|
||||
*/
|
||||
public function getRelationships()
|
||||
{
|
||||
return $this->relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RelationshipResponse $relationships
|
||||
*/
|
||||
public function setRelationships($relationships)
|
||||
{
|
||||
$this->relationships = $relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$response = [
|
||||
'type' => $this->getType(),
|
||||
'id' => $this->getId(),
|
||||
'attributes' => $this->getAttributes(),
|
||||
'relationships' => $this->getRelationships()
|
||||
];
|
||||
|
||||
return array_filter($response);
|
||||
}
|
||||
}
|
82
Api/V8/JsonApi/Response/DocumentResponse.php
Normal file
82
Api/V8/JsonApi/Response/DocumentResponse.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class DocumentResponse implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var DataResponse|DataResponse[]
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @var MetaResponse
|
||||
*/
|
||||
private $meta;
|
||||
|
||||
/**
|
||||
* @var LinksResponse
|
||||
*/
|
||||
private $links;
|
||||
|
||||
/**
|
||||
* @return DataResponse|DataResponse[]
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataResponse|DataResponse[] $data
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MetaResponse
|
||||
*/
|
||||
public function getMeta()
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MetaResponse $meta
|
||||
*/
|
||||
public function setMeta(MetaResponse $meta)
|
||||
{
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LinksResponse
|
||||
*/
|
||||
public function getLinks()
|
||||
{
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinksResponse $links
|
||||
*/
|
||||
public function setLinks($links)
|
||||
{
|
||||
$this->links = $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$response = [
|
||||
'meta' => $this->getMeta(),
|
||||
'data' => $this->getData(),
|
||||
'links' => $this->getLinks(),
|
||||
];
|
||||
|
||||
return array_filter($response);
|
||||
}
|
||||
}
|
84
Api/V8/JsonApi/Response/ErrorResponse.php
Normal file
84
Api/V8/JsonApi/Response/ErrorResponse.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class ErrorResponse implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $status;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $detail;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
*/
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDetail()
|
||||
{
|
||||
return $this->detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $detail
|
||||
*/
|
||||
public function setDetail($detail)
|
||||
{
|
||||
$this->detail = $detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$response = [
|
||||
'errors' => [
|
||||
'status' => $this->getStatus(),
|
||||
'title' => $this->getTitle(),
|
||||
'detail' => $this->getDetail(),
|
||||
]
|
||||
];
|
||||
|
||||
return array_filter($response);
|
||||
}
|
||||
}
|
60
Api/V8/JsonApi/Response/LinksResponse.php
Normal file
60
Api/V8/JsonApi/Response/LinksResponse.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class LinksResponse implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $self;
|
||||
|
||||
/**
|
||||
* @var string|array
|
||||
*/
|
||||
private $related;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSelf()
|
||||
{
|
||||
return $this->self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $self
|
||||
*/
|
||||
public function setSelf($self)
|
||||
{
|
||||
$this->self = $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|string
|
||||
*/
|
||||
public function getRelated()
|
||||
{
|
||||
return $this->related;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $related
|
||||
*/
|
||||
public function setRelated($related)
|
||||
{
|
||||
$this->related = $related;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$response = [
|
||||
'self' => $this->getSelf(),
|
||||
'related' => $this->getRelated()
|
||||
];
|
||||
|
||||
return array_filter($response);
|
||||
}
|
||||
}
|
53
Api/V8/JsonApi/Response/MetaResponse.php
Normal file
53
Api/V8/JsonApi/Response/MetaResponse.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class MetaResponse implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $properties = [];
|
||||
|
||||
/**
|
||||
* Meta object can contain any properties.
|
||||
*
|
||||
* @param array $properties
|
||||
*/
|
||||
public function __construct($properties = [])
|
||||
{
|
||||
if (!is_array($properties) && !$properties instanceof \stdClass) {
|
||||
throw new \InvalidArgumentException('The properties must be an array or sdtClass');
|
||||
}
|
||||
|
||||
foreach ($properties as $name => $value) {
|
||||
$this->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return isset($this->properties[$name]) ? $this->properties[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->properties[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
}
|
102
Api/V8/JsonApi/Response/PaginationResponse.php
Normal file
102
Api/V8/JsonApi/Response/PaginationResponse.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class PaginationResponse extends LinksResponse
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $first;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $prev;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $next;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $last;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFirst()
|
||||
{
|
||||
return $this->first;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $first
|
||||
*/
|
||||
public function setFirst($first)
|
||||
{
|
||||
$this->first = $first;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrev()
|
||||
{
|
||||
return $this->prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prev
|
||||
*/
|
||||
public function setPrev($prev)
|
||||
{
|
||||
$this->prev = $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNext()
|
||||
{
|
||||
return $this->next;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $next
|
||||
*/
|
||||
public function setNext($next)
|
||||
{
|
||||
$this->next = $next;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLast()
|
||||
{
|
||||
return $this->last;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $last
|
||||
*/
|
||||
public function setLast($last)
|
||||
{
|
||||
$this->last = $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'first' => $this->getFirst(),
|
||||
'prev' => $this->getPrev(),
|
||||
'next' => $this->getNext(),
|
||||
'last' => $this->getLast()
|
||||
];
|
||||
}
|
||||
}
|
6
Api/V8/JsonApi/Response/RelationshipResponse.php
Normal file
6
Api/V8/JsonApi/Response/RelationshipResponse.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Api\V8\JsonApi\Response;
|
||||
|
||||
class RelationshipResponse extends MetaResponse
|
||||
{
|
||||
}
|
67
Api/V8/Middleware/ParamsMiddleware.php
Normal file
67
Api/V8/Middleware/ParamsMiddleware.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace Api\V8\Middleware;
|
||||
|
||||
use Api\V8\JsonApi\Response\ErrorResponse;
|
||||
use Api\V8\Param\BaseParam;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class ParamsMiddleware
|
||||
{
|
||||
/**
|
||||
* @var BaseParam
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* @param BaseParam $params
|
||||
*/
|
||||
public function __construct(BaseParam $params)
|
||||
{
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $httpResponse
|
||||
* @param callable $next
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $httpResponse, callable $next)
|
||||
{
|
||||
try {
|
||||
$parameters = $this->getParameters($request);
|
||||
$this->params->configure($parameters);
|
||||
$request = $request->withAttribute('params', $this->params);
|
||||
} catch (\Exception $exception) {
|
||||
$response = new ErrorResponse();
|
||||
$response->setStatus(400);
|
||||
$response->setDetail($exception->getMessage());
|
||||
|
||||
return $httpResponse->withJson($response);
|
||||
}
|
||||
|
||||
return $next($request, $httpResponse);
|
||||
}
|
||||
|
||||
private function getParameters(Request $request)
|
||||
{
|
||||
$routeParams = array_map(
|
||||
function ($value) {
|
||||
return is_bool($value) ? $value : urldecode($value);
|
||||
},
|
||||
$request->getAttribute('route')->getArguments()
|
||||
);
|
||||
|
||||
$queryParams = $request->getQueryParams();
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$parameters = array_merge(
|
||||
$routeParams,
|
||||
isset($queryParams) ? $queryParams : [],
|
||||
isset($parsedBody) ? $parsedBody : []
|
||||
);
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
}
|
2
Api/V8/OAuth2/.gitignore
vendored
Normal file
2
Api/V8/OAuth2/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
private.key
|
||||
public.key
|
12
Api/V8/OAuth2/Entity/AccessTokenEntity.php
Normal file
12
Api/V8/OAuth2/Entity/AccessTokenEntity.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Entity;
|
||||
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
|
||||
|
||||
class AccessTokenEntity implements AccessTokenEntityInterface
|
||||
{
|
||||
use AccessTokenTrait, TokenEntityTrait, EntityTrait;
|
||||
}
|
27
Api/V8/OAuth2/Entity/ClientEntity.php
Normal file
27
Api/V8/OAuth2/Entity/ClientEntity.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Entity;
|
||||
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\ClientTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
|
||||
class ClientEntity implements ClientEntityInterface
|
||||
{
|
||||
use EntityTrait, ClientTrait;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
*/
|
||||
public function setRedirectUri($uri)
|
||||
{
|
||||
$this->redirectUri = $uri;
|
||||
}
|
||||
}
|
11
Api/V8/OAuth2/Entity/RefreshTokenEntity.php
Normal file
11
Api/V8/OAuth2/Entity/RefreshTokenEntity.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Entity;
|
||||
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;
|
||||
|
||||
class RefreshTokenEntity implements RefreshTokenEntityInterface
|
||||
{
|
||||
use RefreshTokenTrait, EntityTrait;
|
||||
}
|
16
Api/V8/OAuth2/Entity/UserEntity.php
Normal file
16
Api/V8/OAuth2/Entity/UserEntity.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Entity;
|
||||
|
||||
use League\OAuth2\Server\Entities\UserEntityInterface;
|
||||
|
||||
class UserEntity implements UserEntityInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getIdentifier()
|
||||
{
|
||||
// we skip this right now, since we are not using scopes atm
|
||||
return true;
|
||||
}
|
||||
}
|
97
Api/V8/OAuth2/Repository/AccessTokenRepository.php
Normal file
97
Api/V8/OAuth2/Repository/AccessTokenRepository.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Repository;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\OAuth2\Entity\AccessTokenEntity;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||
|
||||
class AccessTokenRepository implements AccessTokenRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @var AccessTokenEntity
|
||||
*/
|
||||
private $accessTokenEntity;
|
||||
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @param AccessTokenEntity $accessTokenEntity
|
||||
* @param BeanManager $beanManager
|
||||
*/
|
||||
public function __construct(AccessTokenEntity $accessTokenEntity, BeanManager $beanManager)
|
||||
{
|
||||
$this->accessTokenEntity = $accessTokenEntity;
|
||||
$this->beanManager = $beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null)
|
||||
{
|
||||
$this->accessTokenEntity->setClient($clientEntity);
|
||||
|
||||
// we keep this even we don't have scopes atm
|
||||
foreach ($scopes as $scope) {
|
||||
$this->accessTokenEntity->addScope($scope);
|
||||
}
|
||||
|
||||
$this->accessTokenEntity->setUserIdentifier($userIdentifier);
|
||||
|
||||
return $this->accessTokenEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity)
|
||||
{
|
||||
/** @var \OAuth2Tokens $token */
|
||||
$token = $this->beanManager->newBeanSafe(\OAuth2Tokens::class);
|
||||
$token->access_token = $accessTokenEntity->getIdentifier();
|
||||
$token->access_token_expires = $accessTokenEntity->getExpiryDateTime()->format('Y-m-d H:i:s');
|
||||
$token->client = $accessTokenEntity->getClient()->getIdentifier();
|
||||
|
||||
$token->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function revokeAccessToken($tokenId)
|
||||
{
|
||||
$token = $this->beanManager->newBeanSafe(\OAuth2Tokens::class);
|
||||
$token->retrieve_by_string_fields(
|
||||
['access_token' => $tokenId]
|
||||
);
|
||||
|
||||
if ($token->id === null) {
|
||||
throw new \InvalidArgumentException('Access token is not found for this client');
|
||||
}
|
||||
|
||||
$token->mark_deleted($token->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function isAccessTokenRevoked($tokenId)
|
||||
{
|
||||
/** @var \OAuth2Tokens $token */
|
||||
$token = $this->beanManager->newBeanSafe(\OAuth2Tokens::class);
|
||||
$token->retrieve_by_string_fields(
|
||||
['access_token' => $tokenId]
|
||||
);
|
||||
|
||||
if (new \DateTime() > new \DateTime($token->access_token_expires) || $token->id === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
48
Api/V8/OAuth2/Repository/ClientRepository.php
Normal file
48
Api/V8/OAuth2/Repository/ClientRepository.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Repository;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\OAuth2\Entity\ClientEntity;
|
||||
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
||||
|
||||
class ClientRepository implements ClientRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @var ClientEntity
|
||||
*/
|
||||
private $clientEntity;
|
||||
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @param ClientEntity $clientEntity
|
||||
* @param BeanManager $beanManager
|
||||
*/
|
||||
public function __construct(ClientEntity $clientEntity, BeanManager $beanManager)
|
||||
{
|
||||
$this->clientEntity = $clientEntity;
|
||||
$this->beanManager = $beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getClientEntity($clientIdentifier, $grantType, $clientSecret = null, $mustValidateSecret = true)
|
||||
{
|
||||
/** @var \OAuth2Clients $client */
|
||||
$client = $this->beanManager->getBeanSafe(\OAuth2Clients::class, $clientIdentifier);
|
||||
|
||||
if ($mustValidateSecret && !password_verify($clientSecret, $client->secret)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->clientEntity->setIdentifier($clientIdentifier);
|
||||
$this->clientEntity->setName($client->name);
|
||||
$this->clientEntity->setRedirectUri(isset($client->redirect_uri) ? $client->redirect_uri : '');
|
||||
|
||||
return $this->clientEntity;
|
||||
}
|
||||
}
|
86
Api/V8/OAuth2/Repository/RefreshTokenRepository.php
Normal file
86
Api/V8/OAuth2/Repository/RefreshTokenRepository.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Repository;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\OAuth2\Entity\RefreshTokenEntity;
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
||||
|
||||
class RefreshTokenRepository implements RefreshTokenRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @param BeanManager $beanManager
|
||||
*/
|
||||
public function __construct(BeanManager $beanManager)
|
||||
{
|
||||
$this->beanManager = $beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getNewRefreshToken()
|
||||
{
|
||||
return new RefreshTokenEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity)
|
||||
{
|
||||
/** @var \OAuth2Tokens $token */
|
||||
$token = $this->beanManager->newBeanSafe(\OAuth2Tokens::class);
|
||||
$token->retrieve_by_string_fields(
|
||||
['access_token' => $refreshTokenEntity->getAccessToken()->getIdentifier()]
|
||||
);
|
||||
|
||||
if ($token->id === null) {
|
||||
throw new \InvalidArgumentException('Access token is not found for this client');
|
||||
}
|
||||
|
||||
$token->refresh_token = $refreshTokenEntity->getIdentifier();
|
||||
$token->refresh_token_expires = $refreshTokenEntity->getExpiryDateTime()->format('Y-m-d H:i:s');
|
||||
$token->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function revokeRefreshToken($tokenId)
|
||||
{
|
||||
$token = $this->beanManager->newBeanSafe(\OAuth2Tokens::class);
|
||||
$token->retrieve_by_string_fields(
|
||||
['refresh_token' => $tokenId]
|
||||
);
|
||||
|
||||
if ($token->id === null) {
|
||||
throw new \InvalidArgumentException('Refresh token is not found for this client');
|
||||
}
|
||||
|
||||
$token->mark_deleted($token->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function isRefreshTokenRevoked($tokenId)
|
||||
{
|
||||
/** @var \OAuth2Tokens $token */
|
||||
$token = $this->beanManager->newBeanSafe(\OAuth2Tokens::class);
|
||||
$token->retrieve_by_string_fields(
|
||||
['refresh_token' => $tokenId]
|
||||
);
|
||||
|
||||
if (new \DateTime() > new \DateTime($token->refresh_token_expires) || $token->id === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
28
Api/V8/OAuth2/Repository/ScopeRepository.php
Normal file
28
Api/V8/OAuth2/Repository/ScopeRepository.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Repository;
|
||||
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
||||
|
||||
class ScopeRepository implements ScopeRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getScopeEntityByIdentifier($identifier)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function finalizeScopes(
|
||||
array $scopes,
|
||||
$grantType,
|
||||
ClientEntityInterface $clientEntity,
|
||||
$userIdentifier = null
|
||||
) {
|
||||
// we just return scopes for now
|
||||
return $scopes;
|
||||
}
|
||||
}
|
49
Api/V8/OAuth2/Repository/UserRepository.php
Normal file
49
Api/V8/OAuth2/Repository/UserRepository.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
namespace Api\V8\OAuth2\Repository;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\OAuth2\Entity\UserEntity;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
|
||||
|
||||
class UserRepository implements UserRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @param BeanManager $beanManager
|
||||
*/
|
||||
public function __construct(BeanManager $beanManager)
|
||||
{
|
||||
$this->beanManager = $beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getUserEntityByUserCredentials(
|
||||
$username,
|
||||
$password,
|
||||
$grantType,
|
||||
ClientEntityInterface $clientEntity
|
||||
) {
|
||||
/** @var \User $user */
|
||||
$user = $this->beanManager->newBeanSafe('Users');
|
||||
$user->retrieve_by_string_fields(
|
||||
['user_name' => $username]
|
||||
);
|
||||
|
||||
if ($user->id === null) {
|
||||
throw new \InvalidArgumentException('No user found with this username: ' . $username);
|
||||
}
|
||||
|
||||
if (!\User::checkPassword($password, $user->user_hash)) {
|
||||
throw new \InvalidArgumentException('The password is invalid: ' . $password);
|
||||
}
|
||||
|
||||
return new UserEntity();
|
||||
}
|
||||
}
|
71
Api/V8/Param/BaseParam.php
Normal file
71
Api/V8/Param/BaseParam.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
namespace Api\V8\Param;
|
||||
|
||||
use Api\V8\Factory\ValidatorFactory;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
abstract class BaseParam implements \JsonSerializable
|
||||
{
|
||||
const REGEX_FIELDS_PATTERN = '/[^A-Za-z0-9-_,]/';
|
||||
const REGEX_PAGE_PATTERN = '/^\d+$/';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $parameters = [];
|
||||
|
||||
/**
|
||||
* @var ValidatorFactory
|
||||
*/
|
||||
protected $validatorFactory;
|
||||
|
||||
/**
|
||||
* @param ValidatorFactory $validatorFactory
|
||||
*/
|
||||
public function __construct(ValidatorFactory $validatorFactory)
|
||||
{
|
||||
$this->validatorFactory = $validatorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array ...$arguments
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
final public function configure(...$arguments)
|
||||
{
|
||||
$arguments = array_merge(...$arguments);
|
||||
|
||||
$optionsResolver = new OptionsResolver();
|
||||
$this->configureParameters($optionsResolver);
|
||||
$this->parameters = $optionsResolver->resolve($arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure parameters.
|
||||
*
|
||||
* @param OptionsResolver $resolver
|
||||
*
|
||||
* @throws \Symfony\Component\OptionsResolver\Exception\AccessException In case of invalid access.
|
||||
* @throws \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException In case of invalid option.
|
||||
*/
|
||||
abstract protected function configureParameters(OptionsResolver $resolver);
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
92
Api/V8/Param/ModuleParams.php
Normal file
92
Api/V8/Param/ModuleParams.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
namespace Api\V8\Param;
|
||||
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class ModuleParams extends BaseParam
|
||||
{
|
||||
private static $allowedPageKeys = ['size', 'number'];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getModuleName()
|
||||
{
|
||||
return $this->parameters['moduleName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->parameters['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
return $this->parameters['fields'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPage()
|
||||
{
|
||||
return $this->parameters['page'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function configureParameters(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('moduleName')
|
||||
->setAllowedTypes('moduleName', ['string']);
|
||||
|
||||
$resolver
|
||||
->setDefined('id')
|
||||
->setAllowedTypes('id', ['string'])
|
||||
->setAllowedValues('id', $this->validatorFactory->createClosure([
|
||||
new Assert\NotBlank(),
|
||||
new Assert\Uuid(['strict' => false]),
|
||||
]));
|
||||
|
||||
$resolver
|
||||
->setDefined('fields')
|
||||
->setAllowedTypes('fields', ['array'])
|
||||
->setAllowedValues('fields', $this->validatorFactory->createClosureForIterator([
|
||||
new Assert\NotBlank(),
|
||||
new Assert\Regex([
|
||||
'pattern' => self::REGEX_FIELDS_PATTERN,
|
||||
'match' => false,
|
||||
]),
|
||||
], true));
|
||||
|
||||
$resolver
|
||||
->setDefined('page')
|
||||
->setAllowedTypes('page', ['array'])
|
||||
->setAllowedValues('page', $this->validatorFactory->createClosureForIterator([
|
||||
new Assert\NotBlank(),
|
||||
new Assert\Regex([
|
||||
'pattern' => self::REGEX_PAGE_PATTERN,
|
||||
]),
|
||||
], true))
|
||||
->setNormalizer('page', function (Options $options, $pageKeys) {
|
||||
$invalidKeys = array_diff_key($pageKeys, array_flip(self::$allowedPageKeys));
|
||||
if ($invalidKeys) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid key(s) for page parameter: ' . implode(', ', array_keys($invalidKeys))
|
||||
);
|
||||
}
|
||||
|
||||
return $pageKeys;
|
||||
});
|
||||
}
|
||||
}
|
7
Api/V8/Param/RelationshipParams.php
Normal file
7
Api/V8/Param/RelationshipParams.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
namespace Api\V8\Param;
|
||||
|
||||
class RelationshipParams
|
||||
{
|
||||
|
||||
}
|
48
Api/V8/Service/LogoutService.php
Normal file
48
Api/V8/Service/LogoutService.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
namespace Api\V8\Service;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\JsonApi\Response\DocumentResponse;
|
||||
use Api\V8\JsonApi\Response\MetaResponse;
|
||||
|
||||
class LogoutService
|
||||
{
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @param BeanManager $beanManager
|
||||
*/
|
||||
public function __construct(BeanManager $beanManager)
|
||||
{
|
||||
$this->beanManager = $beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return DocumentResponse
|
||||
*/
|
||||
public function logout($accessToken)
|
||||
{
|
||||
$token = $this->beanManager->newBeanSafe(\OAuth2Tokens::class);
|
||||
$token->retrieve_by_string_fields(
|
||||
['access_token' => $accessToken]
|
||||
);
|
||||
|
||||
if ($token->id === null) {
|
||||
throw new \InvalidArgumentException('Access token is not found for this client');
|
||||
}
|
||||
|
||||
$token->mark_deleted($token->id);
|
||||
|
||||
$response = new DocumentResponse();
|
||||
$response->setMeta(
|
||||
new MetaResponse(['message' => 'You have been successfully logged out'])
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
174
Api/V8/Service/ModuleService.php
Normal file
174
Api/V8/Service/ModuleService.php
Normal file
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
namespace Api\V8\Service;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
use Api\V8\JsonApi\Helper\AttributeObjectHelper;
|
||||
use Api\V8\JsonApi\Helper\RelationshipObjectHelper;
|
||||
use Api\V8\JsonApi\Response\AttributeResponse;
|
||||
use Api\V8\JsonApi\Response\DataResponse;
|
||||
use Api\V8\JsonApi\Response\DocumentResponse;
|
||||
use Api\V8\JsonApi\Response\MetaResponse;
|
||||
use Api\V8\JsonApi\Response\PaginationResponse;
|
||||
use Api\V8\JsonApi\Response\RelationshipResponse;
|
||||
use Api\V8\Param\ModuleParams;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Slim\Http\Request;
|
||||
|
||||
class ModuleService
|
||||
{
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @var AttributeObjectHelper
|
||||
*/
|
||||
private $attributeHelper;
|
||||
|
||||
/**
|
||||
* @var RelationshipObjectHelper
|
||||
*/
|
||||
private $relationshipHelper;
|
||||
|
||||
/**
|
||||
* @param BeanManager $beanManager
|
||||
* @param AttributeObjectHelper $attributeHelper
|
||||
* @param RelationshipObjectHelper $relationshipHelper
|
||||
*/
|
||||
public function __construct(
|
||||
BeanManager $beanManager,
|
||||
AttributeObjectHelper $attributeHelper,
|
||||
RelationshipObjectHelper $relationshipHelper
|
||||
) {
|
||||
$this->beanManager = $beanManager;
|
||||
$this->attributeHelper = $attributeHelper;
|
||||
$this->relationshipHelper = $relationshipHelper;
|
||||
}
|
||||
/**
|
||||
* @param ModuleParams $params
|
||||
* @param UriInterface $uri
|
||||
*
|
||||
* @return DocumentResponse
|
||||
*/
|
||||
public function getRecord(ModuleParams $params, UriInterface $uri)
|
||||
{
|
||||
$bean = $this->beanManager->getBeanSafe(
|
||||
$params->getModuleName(),
|
||||
$params->getId()
|
||||
);
|
||||
|
||||
$dataResponse = $this->getDataResponse($bean, $params, $uri->getPath());
|
||||
$response = new DocumentResponse();
|
||||
$response->setData($dataResponse);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ModuleParams $params
|
||||
* @param Request $request
|
||||
*
|
||||
* @return DocumentResponse
|
||||
*/
|
||||
public function getRecords(ModuleParams $params, Request $request)
|
||||
{
|
||||
$bean = $this->beanManager->findBean($params->getModuleName());
|
||||
$pageParams = $params->getPage();
|
||||
$response = new DocumentResponse();
|
||||
$uriPath = $request->getUri()->getPath();
|
||||
|
||||
// we should really split this into classes asap
|
||||
if (isset($pageParams['size'])) {
|
||||
$pageSize = (int) $pageParams['size'];
|
||||
if ($pageSize > BeanManager::MAX_RECORDS_PER_PAGE) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Maximum allowed page size is ' . BeanManager::MAX_RECORDS_PER_PAGE
|
||||
);
|
||||
}
|
||||
|
||||
$currentPage = (int) $pageParams['number'];
|
||||
$offset = $currentPage !== 0 ? ($currentPage - 1) * $pageSize : $currentPage;
|
||||
|
||||
$beanList = $bean->get_list('', '', $offset, -1, $pageSize);
|
||||
|
||||
if ($currentPage !== 0) {
|
||||
$totalPages = ceil((int) $beanList['row_count'] / $pageSize);
|
||||
if ($beanList['row_count'] <= $offset) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Page not found. Total pages: ' . $totalPages
|
||||
);
|
||||
}
|
||||
|
||||
$response->setMeta(
|
||||
new MetaResponse(['total-pages' => $totalPages])
|
||||
);
|
||||
|
||||
$pagination = new PaginationResponse();
|
||||
$pagination->setFirst($this->createPaginationLink($request, 1));
|
||||
$pagination->setLast($this->createPaginationLink($request, $totalPages));
|
||||
|
||||
if ($currentPage > 1) {
|
||||
$pagination->setPrev($this->createPaginationLink($request, $currentPage - 1));
|
||||
}
|
||||
|
||||
if ($currentPage + 1 <= $totalPages) {
|
||||
$pagination->setNext($this->createPaginationLink($request, $currentPage + 1));
|
||||
}
|
||||
|
||||
$response->setLinks($pagination);
|
||||
}
|
||||
} else {
|
||||
$beanList = $bean->get_list();
|
||||
}
|
||||
|
||||
$data = [];
|
||||
foreach ($beanList['list'] as $record) {
|
||||
$dataResponse = $this->getDataResponse($record, $params, $uriPath);
|
||||
$data[] = $dataResponse;
|
||||
}
|
||||
|
||||
$response->setData($data);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SugarBean $bean
|
||||
* @param ModuleParams $params
|
||||
* @param string $path
|
||||
*
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function getDataResponse(\SugarBean $bean, ModuleParams $params, $path)
|
||||
{
|
||||
$dataResponse = new DataResponse($bean->getObjectName(), $bean->id);
|
||||
|
||||
$attributes = $this->attributeHelper->getAttributes($bean, $params->getFields());
|
||||
$relationships = $this->relationshipHelper->getRelationships($bean, $path, $params->getId());
|
||||
|
||||
if ($attributes) {
|
||||
$dataResponse->setAttributes(new AttributeResponse($attributes));
|
||||
}
|
||||
|
||||
if ($relationships) {
|
||||
$dataResponse->setRelationships(new RelationshipResponse($relationships));
|
||||
}
|
||||
|
||||
return $dataResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param int $number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function createPaginationLink(Request $request, $number)
|
||||
{
|
||||
$queryParams = $request->getQueryParams();
|
||||
$queryParams['page']['number'] = $number;
|
||||
|
||||
return sprintf('/%s?%s', $request->getUri()->getPath(), urldecode(http_build_query($queryParams)));
|
||||
}
|
||||
}
|
20
Api/V8/Service/RelationshipService.php
Normal file
20
Api/V8/Service/RelationshipService.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Api\V8\Service;
|
||||
|
||||
use Api\V8\BeanManager;
|
||||
|
||||
class RelationshipService
|
||||
{
|
||||
/**
|
||||
* @var BeanManager
|
||||
*/
|
||||
private $beanManager;
|
||||
|
||||
/**
|
||||
* @param BeanManager $beanManager
|
||||
*/
|
||||
public function __construct(BeanManager $beanManager)
|
||||
{
|
||||
$this->beanManager = $beanManager;
|
||||
}
|
||||
}
|
4
Api/index.php
Normal file
4
Api/index.php
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
chdir('../');
|
||||
require_once __DIR__ . '/Core/app.php';
|
||||
$app->run();
|
|
@ -21,10 +21,12 @@
|
|||
"tuupola/slim-jwt-auth": "^2.0",
|
||||
"league/url": "^3.3",
|
||||
"psr/log": "^1.0",
|
||||
"league/oauth2-server": "^5.1",
|
||||
"league/oauth2-server": "^6.0",
|
||||
"justinrainbow/json-schema": "^5.2",
|
||||
"onelogin/php-saml": "3.0.0.x-dev as 3.0.0",
|
||||
"google/recaptcha": "^1.1"
|
||||
"google/recaptcha": "^1.1",
|
||||
"symfony/options-resolver": "^3.4",
|
||||
"symfony/validator": "^3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"leafo/scssphp": "dev-master",
|
||||
|
@ -47,7 +49,10 @@
|
|||
"SuiteCRM\\Custom\\": [
|
||||
"custom/lib"
|
||||
]
|
||||
}
|
||||
},
|
||||
"classmap": [
|
||||
"Api/"
|
||||
]
|
||||
},
|
||||
"extra": {}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,16 @@ if (!defined('sugarEntry') || !sugarEntry) {
|
|||
*/
|
||||
class OAuth2Clients extends SugarBean
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $secret;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $redirect_uri;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue