0
0
Fork 0
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:
zoltan kocsardi 2018-04-30 10:59:04 +01:00
parent a9d6169b7f
commit a29cc3a54b
56 changed files with 2344 additions and 3 deletions

7
Api/.htaccess Normal file
View 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
View 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,
]
];

View 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);
}
}

View 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
View 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
View 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
View 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)));
});

View 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');

View file

@ -0,0 +1,11 @@
<?php
return [
'beanAliases' => function () {
return [
'Account' => 'Accounts',
'account' => 'Accounts',
'accounts' => 'Accounts',
];
}
];

View 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)
);
},
];

View 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'));
},
];

View file

@ -0,0 +1,8 @@
<?php
return [
'suiteConfig' => function () {
global $sugar_config;
return $sugar_config;
},
];

View 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)
);
},
];

View 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'
);
},
];

View 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));
}
];

View 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)
);
},
];

View file

@ -0,0 +1,9 @@
<?php
use Symfony\Component\Validator\ValidatorBuilder;
return [
'Validation' => function () {
return (new ValidatorBuilder())->getValidator();
},
];

View 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);
}
}

View file

@ -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);
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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()
{
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace Api\V8\Dictionary;
class Response
{
const MESSAGE_SUCCESS = 'SUCCESS';
const MESSAGE_ERROR = 'ERROR';
}

View 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);
};
}
}

View 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;
};
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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))
);
}
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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()
];
}
}

View file

@ -0,0 +1,6 @@
<?php
namespace Api\V8\JsonApi\Response;
class RelationshipResponse extends MetaResponse
{
}

View 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
View file

@ -0,0 +1,2 @@
private.key
public.key

View 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;
}

View 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;
}
}

View 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;
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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();
}
}

View 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;
});
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace Api\V8\Param;
class RelationshipParams
{
}

View 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;
}
}

View 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)));
}
}

View 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
View file

@ -0,0 +1,4 @@
<?php
chdir('../');
require_once __DIR__ . '/Core/app.php';
$app->run();

View file

@ -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": {}
}

View file

@ -47,6 +47,16 @@ if (!defined('sugarEntry') || !sugarEntry) {
*/
class OAuth2Clients extends SugarBean
{
/**
* @var string
*/
public $secret;
/**
* @var string
*/
public $redirect_uri;
/**
* @var string
*/