0
0
Fork 0
mirror of https://github.com/salesagility/SuiteCRM.git synced 2025-03-17 23:02:43 +00:00

Merge branch 'hotfix' of https://github.com/salesagility/SuiteCRM into hotfix_inboundemail_tests

This commit is contained in:
Gyula Madarasz 2018-10-12 16:18:36 +01:00
commit 9ee11c1c06
284 changed files with 10039 additions and 1243 deletions
.gitignore.travis.yml
Api
Core
V8
BeanDecorator
Config
Controller
Factory
Helper
JsonApi
Middleware
OAuth2
Param
Service
docs
index.php
ModuleInstall
composer.jsoncomposer.lock
data/Relationships
include

1
.gitignore vendored
View file

@ -79,6 +79,7 @@ SuiteCRM.kdev4
vendor/
public/
#Ignore bower_components
bower_components/
node_modules/

View file

@ -22,7 +22,7 @@ addons:
chrome: stable
env:
- INSTANCE_URL=http://localhost/ DATABASE_DRIVER=MYSQL DATABASE_NAME=automated_tests DATABASE_HOST=localhost DATABASE_USER=automated_tests DATABASE_PASSWORD=automated_tests INSTANCE_ADMIN_USER=admin INSTANCE_ADMIN_PASSWORD=admin1 COMPOSER_MEMORY_LIMIT=-1
- INSTANCE_URL=http://localhost DATABASE_DRIVER=MYSQL DATABASE_NAME=automated_tests DATABASE_HOST=localhost DATABASE_USER=automated_tests DATABASE_PASSWORD=automated_tests INSTANCE_ADMIN_USER=admin INSTANCE_ADMIN_PASSWORD=admin1 COMPOSER_MEMORY_LIMIT=-1
before_install:
# Install chrome driver#
@ -53,31 +53,38 @@ before_script:
# Configure apache virtual hosts - images with PHP 7 or above
- sudo cp -f build/travis-ci-apache /etc/apache2/sites-available/000-default.conf
- sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/000-default.conf
- sudo service apache2 restart
# Install composer
- composer install
- ./vendor/bin/codecept build
# Additional PHP config
- phpenv config-add travis.php.ini
- sudo service apache2 restart
# Install composer
- rm composer.lock
- composer install
- ./vendor/bin/codecept build
script:
# Run the wizard installer ?
# Run the wizard installer
- echo "using install wizard"
- ./vendor/bin/codecept run install --env travis-ci-hub -f -vvv -d
- ./build/push_output.sh
# set the log level to error
- echo "\$sugar_config['logger']['level']='error';" >> config_override.php
# Run the unit tests
- ./vendor/bin/codecept run unit --steps -f -vvv -d
# Install OAuth2 demo data
- mysql -u root -D automated_tests -v -e "source tests/_data/api_data.sql"
# Install demo user data
- mysql -u root -D automated_tests -v -e "source tests/_data/demo_users.sql"
# Run the unit tests
# set the log level to error
- echo "\$sugar_config['logger']['level']='error';" >> config_override.php
# add keys
- sudo chmod -R 777 .
- openssl genrsa -out Api/V8/OAuth2/private.key 2048
- openssl rsa -in Api/V8/OAuth2/private.key -pubout -out Api/V8/OAuth2/public.key
- sudo chmod 600 Api/V8/OAuth2/p*.key
# - sudo chown www-data:www-data Api/V8/OAuth2/p*.key
# Run API functional tests
- ./vendor/bin/codecept run tests/api/V8/ -f -vvv -d
# RUN Basic Acceptance test
- ./vendor/bin/codecept run -f unit --steps -v && ./vendor/bin/codecept run api --steps -f -v && sudo chmod -R 777 . && ./vendor/bin/codecept run acceptance --env travis-ci-hub -f -vvv;
- ./vendor/bin/codecept run acceptance --env travis-ci-hub -f -vvv -d
after_script:
- ./build/push_output.sh

View file

@ -0,0 +1,61 @@
<?php
namespace Api\Core\Config;
class ApiConfig
{
// we still support 5.5.9
private static $slimSettings = [
'Api/Core/Config/slim.php',
];
private static $containers = [
'Api/V8/Config/services.php',
];
private static $routes = [
'Api/V8/Config/routes.php',
];
const OAUTH2_PRIVATE_KEY = 'Api/V8/OAuth2/private.key';
const OAUTH2_PUBLIC_KEY = 'Api/V8/OAuth2/public.key';
const OAUTH2_ENCRYPTION_KEY = 'KcWedk/XtvWgtuf7UHx6ayHnrIaMC/t4RjZrdVBY2Ho=';
/**
*
* @var boolean
*/
private static $debugExceptions = false;
/**
*
* @return boolean
*/
public static function getDebugExceptions()
{
return self::$debugExceptions;
}
/**
* @return array
*/
public static function getSlimSettings()
{
return self::$slimSettings;
}
/**
* @return array
*/
public static function getContainers()
{
return self::$containers;
}
/**
* @return array
*/
public static function getRoutes()
{
return self::$routes;
}
}

12
Api/Core/Config/slim.php Normal file
View file

@ -0,0 +1,12 @@
<?php
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
'settings' => [
/** Additional information about exceptions are displayed by the default error handler. */
'displayErrorDetails' => true,
/** Routes are accessible in middleware. */
'determineRouteBeforeAppMiddleware' => true,
]
], basename(__FILE__));

View file

@ -0,0 +1,29 @@
<?php
namespace Api\Core\Loader;
use Api\Core\Config\ApiConfig;
use Api\Core\Resolver\ConfigResolver;
use Interop\Container\ContainerInterface;
use Slim\Container;
class ContainerLoader
{
/**
* Load all service containers and add slim settings
*
* @return ContainerInterface
*/
public static function configure()
{
$slimSettings = ConfigResolver::loadFiles(ApiConfig::getSlimSettings());
// if we want to use this without DI, should create an instance for it
$container = new Container($slimSettings);
$services = ConfigResolver::loadFiles(ApiConfig::getContainers());
foreach ($services as $service => $closure) {
$container[$service] = $closure;
}
return $container;
}
}

View file

@ -0,0 +1,161 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\Core\Loader;
use Exception;
use LoggerManager;
use Slim\App;
/**
* CustomLoader
*
* @author gyula
*/
class CustomLoader
{
const ERR_NO_ERROR = 0;
const ERR_FILE_NOT_FOUND = 1;
const ERR_ROUTE_FILE_NOT_FOUND = 2;
const ERR_WRONG_CUSTOM_FORMAT = 3;
/**
*
* @var int
*/
protected static $lastError = self::ERR_NO_ERROR;
/**
*
* @var string
*/
protected static $customPath = 'custom/application/Ext/Api/V8/';
public static function setCustomPath($customPath = 'custom/application/Ext/Api/V8/')
{
self::$customPath = $customPath;
}
public static function getCustomPath()
{
return self::$customPath;
}
/**
*
* @return int
*/
public static function getLastError()
{
$ret = self::$lastError;
self::$lastError = self::ERR_NO_ERROR;
return $ret;
}
/**
* merge multidimensional arrays
*
* @param array $arrays
* @return array
*/
public static function arrayMerge($arrays)
{
$result = [];
foreach ((array)$arrays as $array) {
foreach ($array as $key => $value) {
if (is_integer($key)) {
// is indexed?
$result[] = $value;
} elseif (isset($result[$key]) && is_array($value) && is_array($result[$key])) {
// is associative?
$result[$key] = self::arrayMerge([$result[$key], $value]);
} else {
$result[$key] = $value;
}
}
}
return $result;
}
/**
* include and merge custom arrays (custom file should return an array)
*
* @param array $array
* @param string $customFile
* @return array
* @throws Exception
*/
public static function mergeCustomArray($array, $customFile)
{
self::getLastError();
$customFile = self::$customPath . $customFile;
if (!file_exists($customFile)) {
self::$lastError = self::ERR_FILE_NOT_FOUND;
LoggerManager::getLogger()->debug('Custom file is not exists: ' . $customFile);
} else {
$customs = include $customFile;
if (!is_array($customs)) {
throw new Exception('Custom file should return an array.', self::ERR_WRONG_CUSTOM_FORMAT);
}
$array = self::arrayMerge([$array, $customs]);
}
return $array;
}
/**
*
* @param App $app
* @return App
*/
public static function loadCustomRoutes(App $app, $customRoutesFile = 'Config/routes.php')
{
self::getLastError();
$customRoutesFile = self::$customPath . $customRoutesFile;
if (!file_exists($customRoutesFile)) {
self::$lastError = self::ERR_ROUTE_FILE_NOT_FOUND;
LoggerManager::getLogger()->debug('Custom routes file is not exists: ' . $customRoutesFile);
} else {
include $customRoutesFile;
}
return $app;
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Api\Core\Loader;
use Api\Core\Config\ApiConfig;
use Api\Core\Resolver\ConfigResolver;
use Slim\App;
class RouteLoader
{
/**
* Load all app routes
*
* @param App $app
*/
public function configureRoutes(App $app)
{
$routes = ApiConfig::getRoutes();
foreach ($routes as $route) {
if (ConfigResolver::isFileExist($route)) {
require $route;
}
}
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Api\Core\Resolver;
class ConfigResolver
{
/**
* Loading and merge files which are arrays.
*
* @param array $files
*
* @return array
* @throws \InvalidArgumentException When config file does not contain an array.
*/
public static function loadFiles(array $files)
{
$configs = [];
foreach ($files as $file) {
// base dir must exist in entryPoint.php
$file = sprintf('%s/%s', $GLOBALS['BASE_DIR'], $file);
if (self::isFileExist($file)) {
$config = require $file;
}
if (!is_array($config)) {
throw new \InvalidArgumentException(sprintf('File %s is invalid', $file));
}
$configs[] = $config;
}
// since we support 5.5.9, we can't use splat op here
return !$configs ? $configs : array_reduce($configs, 'array_merge', []);
}
/**
* @param string $file
*
* @return boolean
* @throws \RuntimeException When config file is not readable.
*/
public static function isFileExist($file)
{
if (!file_exists($file) || !is_readable($file)) {
throw new \RuntimeException(sprintf('File %s is not readable', $file));
}
return true;
}
}

19
Api/Core/app.php Normal file
View file

@ -0,0 +1,19 @@
<?php
// Swagger needs this, but should remove - CORS
header("Access-Control-Allow-Origin: *");
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT');
header('Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With');
// @codingStandardsIgnoreStart
if (!defined('sugarEntry')) {
define('sugarEntry', true);
}
// @codingStandardsIgnoreEnd
chdir(__DIR__ . '/../../');
require_once __DIR__ . '/../../include/entryPoint.php';
$app = new \Slim\App(\Api\Core\Loader\ContainerLoader::configure());
// closure shouldn't be created in static context under PHP7
$routeLoader = new \Api\Core\Loader\RouteLoader();
$routeLoader->configureRoutes($app);

View file

@ -0,0 +1,171 @@
<?php
namespace Api\V8\BeanDecorator;
class BeanListRequest
{
/**
* @var \SugarBean
*/
private $bean;
/**
* @var string
*/
private $orderBy = '';
/**
* @var string
*/
private $where = '';
/**
* @var integer
*/
private $offset = BeanManager::DEFAULT_OFFSET;
/**
* @var integer
*/
private $limit = -1;
/**
* @var integer
*/
private $max = BeanManager::DEFAULT_ALL_RECORDS;
/**
* @var integer
*/
private $deleted = 0;
/**
* @var boolean
*/
private $singleSelect = false;
/**
* @var array
*/
private $fields = [];
/**
* @param \SugarBean $bean
*/
public function __construct(\SugarBean $bean)
{
$this->bean = $bean;
}
/**
* @param string $orderBy
*
* @return BeanListRequest
*/
public function orderBy($orderBy)
{
$this->orderBy = $orderBy;
return $this;
}
/**
* @param string $where
*
* @return BeanListRequest
*/
public function where($where)
{
$this->where = $where;
return $this;
}
/**
* @param integer $offset
*
* @return BeanListRequest
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* @param integer $limit
*
* @return BeanListRequest
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* @param integer $max
*
* @return BeanListRequest
*/
public function max($max)
{
$this->max = $max;
return $this;
}
/**
* @param integer $deleted
*
* @return BeanListRequest
*/
public function deleted($deleted)
{
$this->deleted = $deleted;
return $this;
}
/**
* @param boolean $singleSelect
*
* @return BeanListRequest
*/
public function singleSelect($singleSelect)
{
$this->singleSelect = $singleSelect;
return $this;
}
/**
* @param array $fields
*
* @return BeanListRequest
*/
public function fields(array $fields)
{
$this->fields = $fields;
return $this;
}
/**
* @return BeanListResponse
*/
public function fetch()
{
return new BeanListResponse($this->bean->get_list(
$this->orderBy,
$this->where,
$this->offset,
$this->limit,
$this->max,
$this->deleted,
$this->singleSelect,
$this->fields
));
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Api\V8\BeanDecorator;
class BeanListResponse
{
/**
* @var \SugarBean[]|[]
*/
private $beans;
/**
* @var int
*/
private $rowCount;
/**
* @param array $result
*/
public function __construct(array $result = [])
{
$this->beans = isset($result['list']) ? $result['list'] : [];
$this->rowCount = isset($result['row_count']) ? intval($result['row_count']) : 0;
}
/**
* @return \SugarBean[]|[]
*/
public function getBeans()
{
return $this->beans;
}
/**
* @return integer
*/
public function getRowCount()
{
return $this->rowCount;
}
}

View file

@ -0,0 +1,228 @@
<?php
namespace Api\V8\BeanDecorator;
class BeanManager
{
const DEFAULT_OFFSET = 0;
const DEFAULT_LIMIT = -1;
const DEFAULT_ALL_RECORDS = -99;
/**
* @var \DBManager
*/
private $db;
/**
* @var array
*/
private $beanAliases;
/**
* @param \DBManager $db
* @param array $beanAliases
*/
public function __construct(\DBManager $db, array $beanAliases)
{
$this->db = $db;
$this->beanAliases = $beanAliases;
}
/**
* @param string $module
*
* @return \SugarBean
* @throws \InvalidArgumentException When the module is invalid.
*/
public function newBeanSafe($module)
{
if (array_key_exists($module, $this->beanAliases)) {
$module = $this->beanAliases[$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 boolean $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 get ' . $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 BeanListRequest
*/
public function getList($module)
{
return new BeanListRequest($this->newBeanSafe($module));
}
/**
* @param \SugarBean $sourceBean
* @param \SugarBean $relatedBean
* @param string $relationship
*
* @throws \RuntimeException If relationship cannot be loaded or created between beans.
*/
public function createRelationshipSafe(\SugarBean $sourceBean, \SugarBean $relatedBean, $relationship)
{
if (!$sourceBean->load_relationship($relationship)) {
throw new \RuntimeException(
sprintf('Cannot load relationship %s for module %s', $relationship, $sourceBean->getObjectName())
);
}
$result = $sourceBean->{$relationship}->add($relatedBean);
if (!$result) {
throw new \RuntimeException(
sprintf(
'Cannot create relationship %s between module %s and %s',
$relationship,
$sourceBean->getObjectName(),
$relatedBean->getObjectName()
)
);
}
}
/**
* @param \SugarBean $sourceBean
* @param \SugarBean $relatedBean
* @param string $relationship
*
* @throws \RuntimeException If relationship cannot be loaded or deleted between beans.
*/
public function deleteRelationshipSafe(\SugarBean $sourceBean, \SugarBean $relatedBean, $relationship)
{
if (!$sourceBean->load_relationship($relationship)) {
throw new \RuntimeException(
sprintf('Cannot load relationship %s for module %s', $relationship, $sourceBean->getObjectName())
);
}
$result = $sourceBean->{$relationship}->delete($sourceBean->id, $relatedBean->id);
if (!$result) {
throw new \RuntimeException(
sprintf(
'Cannot delete relationship %s between module %s and %s',
$relationship,
$sourceBean->getObjectName(),
$relatedBean->getObjectName()
)
);
}
}
/**
* @param \SugarBean $sourceBean
* @param \SugarBean $relatedBean
*
* @return string
* @throws \DomainException In case link field is not found.
*/
public function getLinkedFieldName(\SugarBean $sourceBean, \SugarBean $relatedBean)
{
$linkedFields = $sourceBean->get_linked_fields();
$relationship = \Relationship::retrieve_by_modules(
$sourceBean->module_name,
$relatedBean->module_name,
$sourceBean->db
);
$linkFieldName = '';
foreach ($linkedFields as $linkedField) {
if ($linkedField['relationship'] === $relationship) {
$linkFieldName = $linkedField['name'];
}
}
if (!$linkFieldName) {
throw new \DomainException(
sprintf(
'Link field has not found in %s to determine relationship for %s',
$sourceBean->getObjectName(),
$relatedBean->getObjectName()
)
);
}
return $linkFieldName;
}
/**
* @param string $module
* @param string $where
*
* @return integer
*/
public function countRecords($module, $where)
{
$rowCount = $this->db->fetchRow(
$this->db->query(
sprintf(
"SELECT COUNT(*) AS cnt FROM %s %s",
$this->newBeanSafe($module)->getTableName(),
$where === '' ? '' : 'WHERE ' . $where
)
)
)["cnt"];
return intval($rowCount);
}
}

123
Api/V8/Config/routes.php Normal file
View file

@ -0,0 +1,123 @@
<?php
use Api\V8\Controller\LogoutController;
use Api\V8\Factory\ParamsMiddlewareFactory;
use Api\V8\Param\CreateModuleParams;
use Api\V8\Param\CreateRelationshipParams;
use Api\V8\Param\DeleteModuleParams;
use Api\V8\Param\GetModuleParams;
use Api\V8\Param\GetModulesParams;
use Api\V8\Param\GetRelationshipParams;
use Api\V8\Param\ListViewColumnsParams;
use Api\V8\Param\ListViewSearchParams;
use Api\V8\Param\UpdateModuleParams;
use Api\V8\Param\GetUserPreferencesParams;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Middleware\AuthorizationServerMiddleware;
use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
use League\OAuth2\Server\ResourceServer;
use Api\Core\Loader\CustomLoader;
$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);
/**
* Logout
*/
$app->post('/logout', LogoutController::class);
$app
->get('/search-defs/module/{moduleName}', 'Api\V8\Controller\ListViewSearchController:getModuleSearchDefs')
->add($paramsMiddlewareFactory->bind(ListViewSearchParams::class));
$app
->get('/listview/columns/{moduleName}', 'Api\V8\Controller\ListViewController:getListViewColumns')
->add($paramsMiddlewareFactory->bind(ListViewColumnsParams::class));
$app->get('/current-user', 'Api\V8\Controller\UserController:getCurrentUser');
$app
->get('/user-preferences/{id}', 'Api\V8\Controller\UserPreferencesController:getUserPreferences')
->add($paramsMiddlewareFactory->bind(GetUserPreferencesParams::class));
/**
* Get module records
*/
$app
->get('/module/{moduleName}', 'Api\V8\Controller\ModuleController:getModuleRecords')
->add($paramsMiddlewareFactory->bind(GetModulesParams::class));
/**
* Get a module record
*/
$app
->get('/module/{moduleName}/{id}', 'Api\V8\Controller\ModuleController:getModuleRecord')
->add($paramsMiddlewareFactory->bind(GetModuleParams::class));
/**
* Create a module record
*/
$app
->post('/module', 'Api\V8\Controller\ModuleController:createModuleRecord')
->add($paramsMiddlewareFactory->bind(CreateModuleParams::class));
/**
* Update a module record
*/
$app
->patch('/module', 'Api\V8\Controller\ModuleController:updateModuleRecord')
->add($paramsMiddlewareFactory->bind(UpdateModuleParams::class));
/**
* Delete a module record
*/
$app
->delete('/module/{moduleName}/{id}', 'Api\V8\Controller\ModuleController:deleteModuleRecord')
->add($paramsMiddlewareFactory->bind(DeleteModuleParams::class));
/**
* Get relationships
*/
$app
->get(
'/module/{moduleName}/{id}/relationships/{linkFieldName}',
'Api\V8\Controller\RelationshipController:getRelationship'
)
->add($paramsMiddlewareFactory->bind(GetRelationshipParams::class));
/**
* Create relationship
*/
$app
->post(
'/module/{moduleName}/{id}/relationships',
'Api\V8\Controller\RelationshipController:createRelationship'
)
->add($paramsMiddlewareFactory->bind(CreateRelationshipParams::class));
/**
* Delete relationship
*/
$app
->delete(
'/module/{moduleName}/{id}/relationships/{linkFieldName}/{relatedBeanId}',
'Api\V8\Controller\RelationshipController:deleteRelationship'
)
->add($paramsMiddlewareFactory->bind(DeleteRelationShipParams::class));
// add custom routes
$app->group('/custom', function () use ($app) {
$app = CustomLoader::loadCustomRoutes($app);
});
})->add(new ResourceServerMiddleware($app->getContainer()->get(ResourceServer::class)));
});

View file

@ -0,0 +1,28 @@
<?php
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\Controller\InvocationStrategy\SuiteInvocationStrategy;
use Interop\Container\ContainerInterface as Container;
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
/** overwrite slim's foundHandler */
'foundHandler' => function () {
return new SuiteInvocationStrategy();
},
BeanManager::class => function (Container $container) {
return new BeanManager(
$container->get(DBManager::class),
$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'), basename(__FILE__));

View file

@ -0,0 +1,61 @@
<?php
use Api\Core\Loader\CustomLoader;
/**
* Aliases for core modules
*/
return [
'beanAliases' => function () {
return CustomLoader::mergeCustomArray([
Account::class => 'Accounts',
ACLAction::class => 'ACLActions',
ACLRole::class => 'ACLRoles',
Alert::class => 'Alerts',
AOR_Chart::class => 'AOR_Charts',
AOR_Condition::class => 'AOR_Conditions',
AOR_Field::class => 'AOR_Fields',
AOR_Report::class => 'AOR_Reports',
AOW_Action::class => 'AOW_Actions',
AOW_Condition::class => 'AOW_Conditions',
Call::class => 'Calls',
Campaign::class => 'Campaigns',
CampaignTracker::class => 'CampaignTrackers',
aCase::class => 'Cases',
'Case' => 'Cases',
ConnectorRecord::class => 'Connector',
Contact::class => 'Contacts',
Currency::class => 'Currencies',
DocumentRevision::class => 'DocumentRevisions',
Document::class => 'Documents',
FieldsMetaData::class => 'DynamicFields',
Email::class => 'Emails',
EmailTemplate::class => 'EmailTemplates',
Employee::class => 'Employees',
UsersLastImport::class => 'Import',
Lead::class => 'Leads',
Meeting::class => 'Meetings',
MergeRecord::class => 'MergeRecords',
Note::class => 'Notes',
OAuthKey::class => 'OAuthKeys',
OAuthToken::class => 'OAuthTokens',
Opportunity::class => 'Opportunities',
ProspectList::class => 'ProspectLists',
Prospect::class => 'Prospects',
Relationship::class => 'Relationships',
Reminder::class => 'Reminders',
Reminder_Invitee::class => 'Reminders_Invitees',
Role::class => 'Roles',
SecurityGroup::class => 'SecurityGroups',
Task::class => 'Tasks',
Tracker::class => 'Trackers',
User::class => 'Users',
UserPreference::class => 'UserPreferences',
vCal::class => 'vCals',
'Contracts' => AOS_Contracts::class,
'Invoices' => AOS_Invoices::class,
'ProductQuotes' => AOS_Products_Quotes::class,
'Quotes' => AOS_Quotes::class,
], basename(__FILE__));
}
];

View file

@ -0,0 +1,53 @@
<?php
use Api\V8\Controller;
use Api\V8\Service\ListViewSearchService;
use Api\V8\Service\ListViewService;
use Api\V8\Service\LogoutService;
use Api\V8\Service\ModuleService;
use Api\V8\Service\RelationshipService;
use Api\V8\Service\UserPreferencesService;
use Api\V8\Service\UserService;
use Interop\Container\ContainerInterface as Container;
use League\OAuth2\Server\ResourceServer;
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
Controller\ListViewSearchController::class => function (Container $container) {
return new Controller\ListViewSearchController(
$container->get(ListViewSearchService::class)
);
},
Controller\UserPreferencesController::class => function (Container $container) {
return new Controller\UserPreferencesController(
$container->get(UserPreferencesService::class)
);
},
Controller\UserController::class => function (Container $container) {
return new Controller\UserController(
$container->get(UserService::class)
);
},
Controller\ListViewController::class => function (Container $container) {
return new Controller\ListViewController(
$container->get(ListViewService::class)
);
},
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(ResourceServer::class)
);
},
Controller\RelationshipController::class => function (Container $container) {
return new Controller\RelationshipController(
$container->get(RelationshipService::class)
);
},
], basename(__FILE__));

View file

@ -0,0 +1,14 @@
<?php
use Api\V8\Factory;
use Interop\Container\ContainerInterface as Container;
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
Factory\ParamsMiddlewareFactory::class => function (Container $container) {
return new Factory\ParamsMiddlewareFactory($container);
},
Factory\ValidatorFactory::class => function (Container $container) {
return new Factory\ValidatorFactory($container->get('Validation'));
},
], basename(__FILE__));

View file

@ -0,0 +1,13 @@
<?php
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
'suiteConfig' => function () {
global $sugar_config;
return $sugar_config;
},
DBManager::class => function () {
return DBManagerFactory::getInstance();
},
], basename(__FILE__));

View file

@ -0,0 +1,27 @@
<?php
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\Helper;
use Api\V8\JsonApi\Helper as ApiHelper;
use Interop\Container\ContainerInterface as Container;
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
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)
);
},
ApiHelper\PaginationObjectHelper::class => function (Container $container) {
return new ApiHelper\PaginationObjectHelper();
},
], basename(__FILE__));

View file

@ -0,0 +1,66 @@
<?php
use Api\Core\Config\ApiConfig;
use Api\V8\BeanDecorator\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;
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
AuthorizationServer::class => function (Container $container) {
// base dir must exist in entryPoint.php
$baseDir = $GLOBALS['BASE_DIR'];
$server = new AuthorizationServer(
new ClientRepository(
new ClientEntity(),
$container->get(BeanManager::class)
),
new AccessTokenRepository(
new AccessTokenEntity(),
$container->get(BeanManager::class)
),
new ScopeRepository(),
sprintf('file://%s/%s', $baseDir, ApiConfig::OAUTH2_PRIVATE_KEY),
sprintf('file://%s/%s', $baseDir, ApiConfig::OAUTH2_PUBLIC_KEY)
);
$server->setEncryptionKey(ApiConfig::OAUTH2_ENCRYPTION_KEY);
// Client credentials grant
$server->enableGrantType(
new \League\OAuth2\Server\Grant\ClientCredentialsGrant(),
new \DateInterval('PT1H')
);
// Password credentials grant
$server->enableGrantType(
new PasswordGrant(
new UserRepository($container->get(BeanManager::class)),
new RefreshTokenRepository($container->get(BeanManager::class))
),
new \DateInterval('PT1H')
);
return $server;
},
ResourceServer::class => function (Container $container) {
$baseDir = $GLOBALS['BASE_DIR'];
return new ResourceServer(
new AccessTokenRepository(
new AccessTokenEntity(),
$container->get(BeanManager::class)
),
sprintf('file://%s/%s', $baseDir, ApiConfig::OAUTH2_PUBLIC_KEY)
);
},
], basename(__FILE__));

View file

@ -0,0 +1,76 @@
<?php
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\Factory\ValidatorFactory;
use Api\V8\Param;
use Interop\Container\ContainerInterface as Container;
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
Param\ListViewSearchParams::class => function (Container $container) {
return new Param\ListViewSearchParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\GetUserPreferencesParams::class => function (Container $container) {
return new Param\GetUserPreferencesParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\ListViewColumnsParams::class => function (Container $container) {
return new Param\ListViewColumnsParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\GetModuleParams::class => function (Container $container) {
return new Param\GetModuleParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\GetModulesParams::class => function (Container $container) {
return new Param\GetModulesParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\CreateModuleParams::class => function (Container $container) {
return new Param\CreateModuleParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\UpdateModuleParams::class => function (Container $container) {
return new Param\UpdateModuleParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\DeleteModuleParams::class => function (Container $container) {
return new Param\DeleteModuleParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\GetRelationshipParams::class => function (Container $container) {
return new Param\GetRelationshipParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\CreateRelationshipParams::class => function (Container $container) {
return new Param\CreateRelationshipParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
Param\DeleteRelationshipParams::class => function (Container $container) {
return new Param\DeleteRelationshipParams(
$container->get(ValidatorFactory::class),
$container->get(BeanManager::class)
);
},
], basename(__FILE__));

View file

@ -0,0 +1,56 @@
<?php
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\JsonApi\Helper\AttributeObjectHelper;
use Api\V8\JsonApi\Helper\PaginationObjectHelper;
use Api\V8\JsonApi\Helper\RelationshipObjectHelper;
use Api\V8\Service;
use Interop\Container\ContainerInterface as Container;
use Api\Core\Loader\CustomLoader;
return CustomLoader::mergeCustomArray([
Service\ListViewSearchService::class => function (Container $container) {
return new Service\ListViewSearchService(
$container->get(BeanManager::class)
);
},
Service\UserPreferencesService::class => function (Container $container) {
return new Service\UserPreferencesService(
$container->get(BeanManager::class)
);
},
Service\UserService::class => function (Container $container) {
return new Service\UserService(
$container->get(BeanManager::class),
$container->get(AttributeObjectHelper::class),
$container->get(RelationshipObjectHelper::class)
);
},
Service\ListViewService::class => function (Container $container) {
return new Service\ListViewService(
$container->get(BeanManager::class),
$container->get(AttributeObjectHelper::class),
$container->get(RelationshipObjectHelper::class),
$container->get(PaginationObjectHelper::class)
);
},
Service\ModuleService::class => function (Container $container) {
return new Service\ModuleService(
$container->get(BeanManager::class),
$container->get(AttributeObjectHelper::class),
$container->get(RelationshipObjectHelper::class),
$container->get(PaginationObjectHelper::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),
$container->get(AttributeObjectHelper::class)
);
},
], basename(__FILE__));

View file

@ -0,0 +1,11 @@
<?php
use Api\Core\Loader\CustomLoader;
include_once __DIR__ . '/../../../../vendor/symfony/validator/ValidatorBuilder.php';
return CustomLoader::mergeCustomArray([
'Validation' => function () {
return (new Symfony\Component\Validator\ValidatorBuilder())->getValidator();
},
], basename(__FILE__));

View file

@ -0,0 +1,52 @@
<?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 mixed $response
* @param integer $status
*
* @return HttpResponse
*/
public function generateResponse(
HttpResponse $httpResponse,
$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 integer $status
*
* @return HttpResponse
*/
public function generateErrorResponse(HttpResponse $httpResponse, \Exception $exception, $status)
{
$response = new ErrorResponse();
$response->setStatus($status);
$response->setDetail($exception->getMessage());
$response->setException($exception);
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);
}
// since we support 5.5.9, we can't use splat op here
return $callable(
$request,
$response,
$routeArguments,
$request->getAttribute('params') ? $request->getAttribute('params') : null
);
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Controller;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
use Api\V8\Param\ListViewColumnsParams;
use Api\V8\Service\ListViewService;
use Api\V8\Service\ModuleService;
use Exception;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* ListViewController
*
* @author gyula
*/
class ListViewController extends BaseController
{
/**
* @var ListViewService
*/
private $listViewService;
/**
* @param ListViewService $listViewService
*/
public function __construct(ListViewService $listViewService)
{
$this->listViewService = $listViewService;
}
/**
*
* @param Request $request
* @param Response $response
* @param array $args
* @param ListViewColumnsParams $params
* @return HttpResponse
*/
public function getListViewColumns(Request $request, Response $response, array $args, ListViewColumnsParams $params)
{
try {
$jsonResponse = $this->listViewService->getListViewDefs($params);
return $this->generateResponse($response, $jsonResponse, 200);
} catch (Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Controller;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
use Api\V8\Param\ListViewSearchParams;
use Api\V8\Service\ListViewSearchService;
use Exception;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* ListViewSearchController
*
* @author gyula
*/
class ListViewSearchController extends BaseController
{
/**
* @var ListViewSearchService
*/
private $listViewSearchService;
/**
* @param ListViewSearchService $listViewSearchService
*/
public function __construct(ListViewSearchService $listViewSearchService)
{
$this->listViewSearchService = $listViewSearchService;
}
/**
*
* @param Request $request
* @param Response $response
* @param array $args
* @param ListViewSearchParams $params
* @return HttpResponse
*/
public function getModuleSearchDefs(Request $request, Response $response, array $args, ListViewSearchParams $params)
{
try {
$jsonResponse = $this->listViewSearchService->getListViewSearchDefs($params);
return $this->generateResponse($response, $jsonResponse, 200);
} catch (Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
}

View file

@ -0,0 +1,51 @@
<?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
{
/**
* @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
*
* @return Response
*/
public function __invoke(Request $request, Response $response)
{
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,122 @@
<?php
namespace Api\V8\Controller;
use Api\V8\Param\CreateModuleParams;
use Api\V8\Param\DeleteModuleParams;
use Api\V8\Param\GetModuleParams;
use Api\V8\Param\GetModulesParams;
use Api\V8\Param\UpdateModuleParams;
use Api\V8\Service\ModuleService;
use Slim\Http\Request;
use Slim\Http\Response;
class ModuleController extends BaseController
{
/**
* @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 GetModuleParams $params
*
* @return Response
*/
public function getModuleRecord(Request $request, Response $response, array $args, GetModuleParams $params)
{
try {
$jsonResponse = $this->moduleService->getRecord($params, $request->getUri()->getPath());
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 GetModulesParams $params
*
* @return Response
*/
public function getModuleRecords(Request $request, Response $response, array $args, GetModulesParams $params)
{
try {
$jsonResponse = $this->moduleService->getRecords($params, $request);
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 CreateModuleParams $params
*
* @return Response
*/
public function createModuleRecord(Request $request, Response $response, array $args, CreateModuleParams $params)
{
try {
$jsonResponse = $this->moduleService->createRecord($params, $request);
return $this->generateResponse($response, $jsonResponse, 201);
} catch (\Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
/**
* @param Request $request
* @param Response $response
* @param array $args
* @param UpdateModuleParams $params
*
* @return Response
*/
public function updateModuleRecord(Request $request, Response $response, array $args, UpdateModuleParams $params)
{
try {
$jsonResponse = $this->moduleService->updateRecord($params, $request);
return $this->generateResponse($response, $jsonResponse, 201);
} catch (\Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
/**
* @param Request $request
* @param Response $response
* @param array $args
* @param DeleteModuleParams $params
*
* @return Response
*/
public function deleteModuleRecord(Request $request, Response $response, array $args, DeleteModuleParams $params)
{
try {
$jsonResponse = $this->moduleService->deleteRecord($params);
return $this->generateResponse($response, $jsonResponse, 200);
} catch (\Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Api\V8\Controller;
use Api\V8\Param\CreateRelationshipParams;
use Api\V8\Param\DeleteRelationshipParams;
use Api\V8\Param\GetRelationshipParams;
use Api\V8\Service\RelationshipService;
use Slim\Http\Request;
use Slim\Http\Response;
class RelationshipController extends BaseController
{
/**
* @var RelationshipService
*/
private $relationshipService;
/**
* @param RelationshipService $relationshipService
*/
public function __construct(RelationshipService $relationshipService)
{
$this->relationshipService = $relationshipService;
}
/**
* @param Request $request
* @param Response $response
* @param array $args
* @param GetRelationshipParams $params
*
* @return Response
*/
public function getRelationship(Request $request, Response $response, array $args, GetRelationshipParams $params)
{
try {
$jsonResponse = $this->relationshipService->getRelationship($params);
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 CreateRelationshipParams $params
*
* @return Response
*/
public function createRelationship(
Request $request,
Response $response,
array $args,
CreateRelationshipParams $params
) {
try {
$jsonResponse = $this->relationshipService->createRelationship($params);
return $this->generateResponse($response, $jsonResponse, 201);
} catch (\Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
/**
* @param Request $request
* @param Response $response
* @param array $args
* @param DeleteRelationshipParams $params
*
* @return Response
*/
public function deleteRelationship(
Request $request,
Response $response,
array $args,
DeleteRelationshipParams $params
) {
try {
$jsonResponse = $this->relationshipService->deleteRelationship($params);
return $this->generateResponse($response, $jsonResponse, 200);
} catch (\Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Controller;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
use Api\V8\Service\UserService;
use Exception;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* UserController
*
* @author gyula
*/
class UserController extends BaseController
{
/**
* @var UserService
*/
private $userService;
/**
* @param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
/**
*
* @param Request $request
* @param Response $response
* @param array $args
* @return Response
*/
public function getCurrentUser(Request $request, Response $response, array $args)
{
try {
$jsonResponse = $this->userService->getCurrentUser($request);
return $this->generateResponse($response, $jsonResponse, 200);
} catch (Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Controller;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
use Api\V8\Param\GetUserPreferencesParams;
use Api\V8\Service\UserPreferencesService;
use Exception;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* UserPreferencesController
*
* @author gyula
*/
class UserPreferencesController extends BaseController
{
/**
* @var UserPreferencesService
*/
private $userPreferencesService;
/**
* @param UserPreferencesService $userPreferencesService
*/
public function __construct(UserPreferencesService $userPreferencesService)
{
$this->userPreferencesService = $userPreferencesService;
}
/**
*
* @param Request $request
* @param Response $response
* @param array $args
* @param GetUserPreferencesParams $params
* @return Response
*/
public function getUserPreferences(Request $request, Response $response, array $args, GetUserPreferencesParams $params)
{
try {
$jsonResponse = $this->userPreferencesService->getUserPreferences($params);
return $this->generateResponse($response, $jsonResponse, 200);
} catch (Exception $exception) {
return $this->generateErrorResponse($response, $exception, 400);
}
}
}

View file

@ -0,0 +1,39 @@
<?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
*/
protected $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) {
$paramMiddleware = new ParamsMiddleware($container->get($containerId));
return $paramMiddleware($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,49 @@
<?php
namespace Api\V8\JsonApi\Helper;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\JsonApi\Response\AttributeResponse;
class AttributeObjectHelper
{
/**
* @var BeanManager
*/
private $beanManager;
/**
* @param BeanManager $beanManager
*/
public function __construct(BeanManager $beanManager)
{
$this->beanManager = $beanManager;
}
/**
* @param \SugarBean $bean
* @param array|null $fields
*
* @return AttributeResponse
*/
public function getAttributes(\SugarBean $bean, $fields = null)
{
$bean->fixUpFormatting();
// using the ISO 8601 format for dates
$attributes = array_map(function ($value) {
return is_string($value)
? (\DateTime::createFromFormat('Y-m-d H:i:s', $value)
? date(\DateTime::ATOM, strtotime($value))
: $value)
: $value;
}, $bean->toArray());
if ($fields !== null) {
$attributes = array_intersect_key($attributes, array_flip($fields));
}
unset($attributes['id']);
return new AttributeResponse($attributes);
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Api\V8\JsonApi\Helper;
use Api\V8\JsonApi\Response\MetaResponse;
use Api\V8\JsonApi\Response\PaginationResponse;
use Slim\Http\Request;
class PaginationObjectHelper
{
/**
* @param integer $totalPages
* @param integer $numOfRecords
*
* @return MetaResponse
*/
public function getPaginationMeta($totalPages, $numOfRecords)
{
return new MetaResponse(
['total-pages' => $totalPages, 'records-on-this-page' => $numOfRecords]
);
}
/**
* @param Request $request
* @param integer $totalPages
* @param integer $number
*
* @return PaginationResponse
*/
public function getPaginationLinks(Request $request, $totalPages, $number)
{
$pagination = new PaginationResponse();
if ($number > 1) {
$pagination->setFirst($this->createPaginationLink($request, 1));
$pagination->setPrev($this->createPaginationLink($request, $number - 1));
}
if ($number + 1 <= $totalPages) {
$pagination->setNext($this->createPaginationLink($request, $number + 1));
$pagination->setLast($this->createPaginationLink($request, $totalPages));
}
return $pagination;
}
/**
* @param Request $request
* @param integer $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,46 @@
<?php
namespace Api\V8\JsonApi\Helper;
use Api\V8\Helper\VarDefHelper;
use Api\V8\JsonApi\Response\LinksResponse;
use Api\V8\JsonApi\Response\RelationshipResponse;
class RelationshipObjectHelper
{
/**
* @var VarDefHelper
*/
private $varDefHelper;
/**
* @param VarDefHelper $varDefHelper
*/
public function __construct(VarDefHelper $varDefHelper)
{
$this->varDefHelper = $varDefHelper;
}
/**
* @param \SugarBean $bean
* @param string $uriPath
*
* @return RelationshipResponse
*/
public function getRelationships(\SugarBean $bean, $uriPath)
{
$relationships = $this->varDefHelper->getAllRelationships($bean);
asort($relationships);
$relationshipsLinks = [];
foreach (array_unique($relationships) as $module) {
$linkResponse = new LinksResponse();
$linkResponse->setRelated(
sprintf('/%s/%s/%s', $uriPath, 'relationships', strtolower($module))
);
$relationshipsLinks[$module] = ['links' => $linkResponse];
}
return new RelationshipResponse($relationshipsLinks);
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Api\V8\JsonApi\Repository;
class Filter
{
// operators so far
const OP_EQ = '=';
const OP_NEQ = '<>';
const OP_GT = '>';
const OP_GTE = '>=';
const OP_LT = '<';
const OP_LTE = '<=';
const OP_AND = 'AND';
const OP_OR = 'OR';
/**
* @var \DBManager
*/
private $db;
/**
* @param \DBManager $db
*/
public function __construct(\DBManager $db)
{
$this->db = $db;
}
/**
* @param \SugarBean $bean
* @param array $params
*
* @return string
* @throws \InvalidArgumentException When field is not found or is not an array.
*/
public function parseWhere(\SugarBean $bean, array $params)
{
$operator = self::OP_AND;
if (isset($params['operator'])) {
$this->checkOperator($params['operator']);
$operator = strtoupper($params['operator']);
unset($params['operator']);
}
$where = [];
foreach ($params as $field => $expr) {
if (!property_exists($bean, $field)) {
throw new \InvalidArgumentException(sprintf(
'Filter field %s in %s module is not found',
$field,
$bean->getObjectName()
));
}
if (!is_array($expr)) {
throw new \InvalidArgumentException(sprintf('Filter field %s must be an array', $field));
}
foreach ($expr as $op => $value) {
$this->checkOperator($op);
$where[] = sprintf(
'%s.%s %s %s',
$bean->getTableName(),
$field,
constant(sprintf('%s::OP_%s', self::class, strtoupper($op))),
$this->db->quoted($value)
);
}
}
return implode(sprintf(' %s ', $operator), $where);
}
/**
* @param string $op
*
* @throws \InvalidArgumentException When the given operator is invalid.
*/
private function checkOperator($op)
{
$operator = sprintf('%s::OP_%s', self::class, strtoupper($op));
if (!defined($operator)) {
throw new \InvalidArgumentException(
sprintf('Filter operator %s is invalid', $op)
);
}
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Api\V8\JsonApi\Repository;
class Sort
{
const ORDER_BY_ASC = 'ASC';
const ORDER_BY_DESC = 'DESC';
/**
* We don't support multiple sorting. for now.
*
* @param \SugarBean $bean
* @param string $value
*
* @return string
* @throws \InvalidArgumentException When sort field is not found in the bean.
*/
public function parseOrderBy(\SugarBean $bean, $value)
{
$orderBy = self::ORDER_BY_ASC;
if ($value[0] === '-') {
$orderBy = self::ORDER_BY_DESC;
$value = ltrim($value, '-');
}
if (!property_exists($bean, $value)) {
throw new \InvalidArgumentException(sprintf(
'Sort field %s in %s module is not found',
$value,
$bean->getObjectName()
));
}
return sprintf('%s %s', $value, $orderBy);
}
}

View file

@ -0,0 +1,29 @@
<?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|\stdClass $properties
*
* @throws \InvalidArgumentException When attribute object includes forbidden keys.
*/
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,120 @@
<?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;
/**
* @var LinksResponse
*/
private $links;
/**
* @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(RelationshipResponse $relationships)
{
$this->relationships = $relationships;
}
/**
* @return LinksResponse
*/
public function getLinks()
{
return $this->links;
}
/**
* @param LinksResponse $links
*/
public function setLinks(LinksResponse $links)
{
$this->links = $links;
}
/**
* @inheritdoc
*/
public function jsonSerialize()
{
$response = [
'type' => $this->getType(),
'id' => $this->getId(),
'attributes' => $this->getAttributes(),
'relationships' => $this->getRelationships(),
'links' => $this->getLinks()
];
return array_filter($response);
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Api\V8\JsonApi\Response;
class DocumentResponse implements \JsonSerializable
{
/**
* @var array|DataResponse|DataResponse[]
*/
private $data = [];
/**
* @var MetaResponse
*/
private $meta;
/**
* @var LinksResponse
*/
private $links;
/**
* @return array|DataResponse|DataResponse[]
*/
public function getData()
{
return $this->data;
}
/**
* @param array|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(LinksResponse $links)
{
$this->links = $links;
}
/**
* @inheritdoc
*/
public function jsonSerialize()
{
$response = [
'data' => $this->getData()
];
if (!$this->getData() && !$this->getMeta()) {
$this->setMeta(new MetaResponse(['message' => 'Request was successful, but there is no result']));
}
if ($this->getMeta()) {
$response = ['meta' => $this->getMeta()] + $response;
}
if ($this->getLinks()) {
$response['links'] = $this->getLinks();
}
return $response;
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Api\V8\JsonApi\Response;
use Api\Core\Config\ApiConfig;
use Exception;
use JsonSerializable;
class ErrorResponse implements JsonSerializable
{
/**
* @var integer
*/
private $status;
/**
* @var string
*/
private $title;
/**
* @var string
*/
private $detail;
/**
*
* @var Exception
*/
private $exception;
/**
* In debug mode, ErrorResponse should shows full description about occurred exceptions.
*
* @todo documentation needs to be updated at this point (about debug exceptions)
* @var boolean
*/
protected $debugExceptions;
/**
*
* @param bool|null $debugExceptions optional - using ApiConfig setting by default
*/
public function __construct($debugExceptions = null)
{
$this->debugExceptions =
null === $debugExceptions ?
ApiConfig::getDebugExceptions() :
$debugExceptions;
}
/**
* @return integer
*/
public function getStatus()
{
return $this->status;
}
/**
* @param integer $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;
}
/**
*
* @param Exception $exception
*/
public function setException(Exception $exception)
{
$this->exception = $exception;
}
/**
*
* @param Exception $exception
* @return array
*/
protected static function exceptionToArray(Exception $exception)
{
return [
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $exception->getMessage(),
'previous' => self::exceptionToArray($exception->getPrevious()),
'trace' => $exception->getTrace(),
'traceAsString' => $exception->getTraceAsString(),
];
}
/**
*
* @return array
*/
public function getExceptionArray()
{
if (!$this->exception) {
return null;
}
return self::exceptionToArray($this->exception);
}
/**
* @inheritdoc
*/
public function jsonSerialize()
{
$ret = [
'errors' => [
'status' => $this->getStatus(),
'title' => $this->getTitle(),
'detail' => $this->getDetail(),
]
];
// do it only in debug mode!!!!
if ($this->debugExceptions) {
$ret['errors']['exception'] = $this->getExceptionArray();
}
return $ret;
}
}

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,55 @@
<?php
namespace Api\V8\JsonApi\Response;
class MetaResponse implements \JsonSerializable
{
/**
* @var array
*/
protected $properties = [];
/**
* Meta object can contain any properties.
*
* @param array|\stdClass $properties
*
* @throws \InvalidArgumentException When bean is not found with the given id.
*/
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 $property => $value) {
$this->$property = $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,83 @@
<?php
namespace Api\V8\Middleware;
use Api\V8\JsonApi\Response\ErrorResponse;
use Api\V8\Param\BaseParam;
use Exception;
use LoggerManager;
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);
$msg = $exception->getMessage();
$dbg = "\nCode:" . $exception->getCode() .
"\n" . $exception->getFile() . ':' . $exception->getLine() .
"\nTrace:\n" . $exception->getTraceAsString() .
"\n";
LoggerManager::getLogger()->fatal("API Exception detected:\nMessage was: $msg\nException details:\n$dbg");
$response->setDetail($msg);
return $httpResponse->withJson(
$response,
400,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
}
return $next($request, $httpResponse);
}
/**
* @param Request $request
*
* @return array
*/
protected 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();
return array_merge(
$routeParams,
isset($queryParams) ? $queryParams : [],
isset($parsedBody) ? $parsedBody : []
);
}
}

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,99 @@
<?php
namespace Api\V8\OAuth2\Repository;
use Api\V8\BeanDecorator\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
*
* @throws \InvalidArgumentException When access token is not found.
*/
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,47 @@
<?php
namespace Api\V8\OAuth2\Repository;
use Api\V8\BeanDecorator\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 && hash('sha256', $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,90 @@
<?php
namespace Api\V8\OAuth2\Repository;
use Api\V8\BeanDecorator\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
*
* @throws \InvalidArgumentException When access token is not found.
*/
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
*
* @throws \InvalidArgumentException When refresh token is not found.
*/
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,51 @@
<?php
namespace Api\V8\OAuth2\Repository;
use Api\V8\BeanDecorator\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
*
* @throws \InvalidArgumentException If user does not exist or the password is invalid.
*/
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,98 @@
<?php
namespace Api\V8\Param;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\Factory\ValidatorFactory;
use Api\V8\Param\Options\BaseOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
abstract class BaseParam implements \JsonSerializable
{
/**
* @var array
*/
protected $parameters = [];
/**
* @var ValidatorFactory
*/
protected $validatorFactory;
/**
* @var BeanManager
*/
protected $beanManager;
/**
* @param ValidatorFactory $validatorFactory
* @param BeanManager $beanManager
*/
public function __construct(ValidatorFactory $validatorFactory, BeanManager $beanManager)
{
$this->validatorFactory = $validatorFactory;
$this->beanManager = $beanManager;
}
/**
* @param array $arguments
*
* @return self
*/
final public function configure(array $arguments)
{
$optionsResolver = new OptionsResolver();
$this->setDefined($optionsResolver, $arguments);
$this->configureParameters($optionsResolver);
$this->parameters = $optionsResolver->resolve($arguments);
return $this;
}
/**
* We can overwrite this method, if necessary
*
* @param OptionsResolver $resolver
* @param array $arguments
*/
public function setDefined(OptionsResolver $resolver, array $arguments)
{
}
/**
* Configure parameters.
*
* @param OptionsResolver $resolver
*
* @return void
*/
abstract protected function configureParameters(OptionsResolver $resolver);
/**
* Configure already defined options.
*
* @param OptionsResolver $optionResolver
* @param array $options
*
* @throws \InvalidArgumentException If option is not exist.
*/
protected function setOptions(OptionsResolver $optionResolver, array $options)
{
foreach ($options as $key => $option) {
if (!class_exists($option)) {
throw new \InvalidArgumentException(sprintf('Option %s does not exist!', $option));
}
/** @var BaseOption $class */
$class = new $option($this->validatorFactory, $this->beanManager);
$class->add($optionResolver);
}
}
/**
* @inheritdoc
*/
public function jsonSerialize()
{
return $this->parameters;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class CreateModuleDataParams extends BaseParam
{
/**
* @return string
*/
public function getType()
{
return $this->parameters['type'];
}
/**
* @return string|null
*/
public function getId()
{
return isset($this->parameters['id']) ? $this->parameters['id'] : null;
}
/**
* @return array
*/
public function getAttributes()
{
return isset($this->parameters['attributes']) ? $this->parameters['attributes'] : [];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
// need to make this more simple
$resolver
->setDefined('id')
->setAllowedTypes('id', 'string')
->setAllowedValues('id', $this->validatorFactory->createClosure([
new Assert\NotBlank(),
new Assert\Uuid(['strict' => false]),
]));
$this->setOptions(
$resolver,
[
ParamOption\Type::class,
ParamOption\Attributes::class,
]
);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Api\V8\Param;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class CreateModuleParams extends BaseParam
{
/**
* @return CreateModuleDataParams
*/
public function getData()
{
return $this->parameters['data'];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$resolver
->setRequired('data')
->setAllowedTypes('data', 'array')
->setAllowedValues('data', $this->validatorFactory->createClosureForIterator([
new Assert\NotBlank(),
]))
->setNormalizer('data', function (Options $options, $values) {
$dataParams = new CreateModuleDataParams($this->validatorFactory, $this->beanManager);
$dataParams->configure($values);
return $dataParams;
});
}
}

View file

@ -0,0 +1,99 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class CreateRelationshipParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
* @return string
*/
public function getId()
{
return $this->parameters['id'];
}
/**
* @return GetRelationshipDataParams
*/
public function getData()
{
return $this->parameters['data'];
}
/**
* @return \SugarBean
*/
public function getSourceBean()
{
return $this->parameters['sourceBean'];
}
/**
* @return \SugarBean
*/
public function getRelatedBean()
{
return $this->parameters['relatedBean'];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
ParamOption\Id::class,
]
);
$resolver
->setRequired('data')
->setAllowedTypes('data', 'array')
->setAllowedValues('data', $this->validatorFactory->createClosureForIterator([
new Assert\NotBlank(),
]))
->setNormalizer('data', function (Options $options, $value) {
$dataParams = new GetRelationshipDataParams($this->validatorFactory, $this->beanManager);
$dataParams->configure($value);
return $dataParams;
});
$resolver
->setDefined('sourceBean')
->setDefault('sourceBean', function (Options $options) {
return $this->beanManager->getBeanSafe(
$options->offsetGet('moduleName'),
$options->offsetGet('id')
);
})
->setAllowedTypes('sourceBean', \SugarBean::class);
$resolver
->setDefined('relatedBean')
->setDefault('relatedBean', function (Options $options) {
$dataParams = $options->offsetGet('data');
return $this->beanManager->getBeanSafe(
$dataParams->getType(),
$dataParams->getId()
);
})
->setAllowedTypes('relatedBean', \SugarBean::class);
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DeleteModuleParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
* @return string
*/
public function getId()
{
return $this->parameters['id'];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
ParamOption\Id::class,
]
);
}
}

View file

@ -0,0 +1,85 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class DeleteRelationshipParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
* @return string
*/
public function getId()
{
return $this->parameters['id'];
}
/**
* @return string
*/
public function getLinkedFieldName()
{
return $this->parameters['linkFieldName'];
}
/**
* @return string
*/
public function getRelatedBeanId()
{
return $this->parameters['relatedBeanId'];
}
/**
* @return \SugarBean
*/
public function getSourceBean()
{
return $this->parameters['sourceBean'];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
ParamOption\Id::class,
]
);
$resolver
->setRequired('relatedBeanId')
->setAllowedTypes('relatedBeanId', 'string')
->setAllowedValues('relatedBeanId', $this->validatorFactory->createClosure([
new Assert\NotBlank(),
new Assert\Uuid(['strict' => false]),
]));
$resolver
->setDefined('sourceBean')
->setDefault('sourceBean', function (Options $options) {
return $this->beanManager->getBeanSafe(
$options->offsetGet('moduleName'),
$options->offsetGet('id')
);
})
->setAllowedTypes('sourceBean', \SugarBean::class);
// dependency on sourceBean field
$this->setOptions($resolver, [ParamOption\LinkFieldName::class]);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
class GetModuleParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
* @return string
*/
public function getId()
{
return $this->parameters['id'];
}
/**
* @return array|null
*/
public function getFields()
{
return isset($this->parameters['fields']) ? $this->parameters['fields'] : null;
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
ParamOption\Id::class,
ParamOption\Fields::class,
]
);
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
class GetModulesParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
* @return array|null
*/
public function getFields()
{
return isset($this->parameters['fields']) ? $this->parameters['fields'] : null;
}
/**
* @return PageParams
*/
public function getPage()
{
return isset($this->parameters['page'])
? $this->parameters['page']
: new PageParams($this->validatorFactory, $this->beanManager);
}
/**
* @return string
*/
public function getSort()
{
return isset($this->parameters['sort']) ? $this->parameters['sort'] : '';
}
/**
* @return string
*/
public function getFilter()
{
return isset($this->parameters['filter']) ? $this->parameters['filter'] : '';
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
ParamOption\Fields::class,
ParamOption\Page::class,
ParamOption\Sort::class,
ParamOption\Filter::class
]
);
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
class GetRelationshipDataParams extends BaseParam
{
/**
* @return string
*/
public function getType()
{
return $this->parameters['type'];
}
/**
* @return string
*/
public function getId()
{
return $this->parameters['id'];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\Type::class,
ParamOption\Id::class,
]
);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class GetRelationshipParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
* @return string
*/
public function getId()
{
return $this->parameters['id'];
}
/**
* @return string
*/
public function getLinkedFieldName()
{
return $this->parameters['linkFieldName'];
}
/**
* @return \SugarBean
*/
public function getSourceBean()
{
return $this->parameters['sourceBean'];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
ParamOption\Id::class,
]
);
$resolver
->setDefined('sourceBean')
->setDefault('sourceBean', function (Options $options) {
return $this->beanManager->getBeanSafe(
$options->offsetGet('moduleName'),
$options->offsetGet('id')
);
})
->setAllowedTypes('sourceBean', [\SugarBean::class]);
// dependency on sourceBean field
$this->setOptions($resolver, [ParamOption\LinkFieldName::class]);
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Param;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* GetUserPreferencesParams
*
* @author gyula
*/
class GetUserPreferencesParams extends BaseParam
{
/**
* @return string
*/
public function getUserId()
{
return $this->parameters['id'];
}
/**
*
* @param OptionsResolver $resolver
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions($resolver, [ParamOption\Id::class]);
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Api\V8\Param\OptionsResolver;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
/**
* ListViewColumnsParams
*
* @author gyula
*/
class ListViewColumnsParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
*
* @param \Api\V8\Param\OptionsResolver $resolver
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
]
);
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Param;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* ListViewSearchParams
*
* @author gyula
*/
class ListViewSearchParams extends BaseParam
{
/**
* @return string
*/
public function getModuleName()
{
return $this->parameters['moduleName'];
}
/**
*
* @param OptionsResolver $resolver
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\ModuleName::class,
]
);
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Api\V8\Param\Options;
use InvalidArgumentException;
use OutOfBoundsException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class Attributes extends BaseOption
{
/**
* @inheritdoc
*
* @throws InvalidArgumentException If attributes parameters have invalid property.
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setDefined('attributes')
->setAllowedTypes('attributes', 'array')
->setAllowedValues('attributes', $this->validatorFactory->createClosureForIterator([
new Assert\NotBlank(),
new Assert\Regex([
'pattern' => Fields::REGEX_FIELD_PATTERN,
'match' => false,
]),
]))
->setNormalizer('attributes', function (Options $options, $values) {
$bean = $this->beanManager->newBeanSafe($options->offsetGet('type'));
foreach ($values as $attribute => $value) {
$invalidProperty =
!property_exists($bean, $attribute) &&
!array_key_exists($attribute, $bean->field_defs) &&
!array_key_exists($attribute, $bean->field_name_map);
if ($invalidProperty) {
throw new OutOfBoundsException(sprintf(
'Property %s in %s module is invalid',
$attribute,
$bean->getObjectName()
));
}
}
return $values;
});
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Api\V8\Param\Options;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\Factory\ValidatorFactory;
use Symfony\Component\OptionsResolver\OptionsResolver;
abstract class BaseOption
{
/**
* @var ValidatorFactory
*/
protected $validatorFactory;
/**
* @var BeanManager
*/
protected $beanManager;
/**
* @param ValidatorFactory $validatorFactory
* @param BeanManager $beanManager
*/
public function __construct(ValidatorFactory $validatorFactory, BeanManager $beanManager)
{
$this->validatorFactory = $validatorFactory;
$this->beanManager = $beanManager;
}
/**
* @param OptionsResolver $resolver
*
* @return void
*/
abstract public function add(OptionsResolver $resolver);
/**
* @param string $class
* @see https://github.com/rappasoft/laravel-helpers#class_basename
*
* @return string
*/
protected function getOptionName($class)
{
return lcfirst(basename(str_replace('\\', '/', $class)));
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Api\V8\Param\Options;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class Fields extends BaseOption
{
const REGEX_FIELD_PATTERN = '/[^\w-,]/';
/**
* @inheritdoc
*
* @throws \InvalidArgumentException In case fields are invalid.
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setDefined('fields')
->setAllowedTypes('fields', 'array')
->setAllowedValues('fields', $this->validatorFactory->createClosureForIterator([
new Assert\NotBlank(),
new Assert\Regex([
'pattern' => self::REGEX_FIELD_PATTERN,
'match' => false,
]),
], true))
->setNormalizer('fields', function (Options $options, $values) {
$bean = $this->beanManager->newBeanSafe(key($values));
$attributes = $bean->toArray();
$fields = explode(',', array_shift($values));
$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' : '',
$bean->getObjectName(),
count($invalidFields) > 1 ? 'are' : 'is',
implode(', ', $invalidFields)
)
);
}
return $fields;
});
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Api\V8\Param\Options;
use Api\V8\JsonApi\Repository\Filter as FilterRepository;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class Filter extends BaseOption
{
/**
* @inheritdoc
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setDefined('filter')
->setAllowedTypes('filter', 'array')
->setAllowedValues('filter', $this->validatorFactory->createClosure([
new Assert\NotBlank(),
]))
->setNormalizer('filter', function (Options $options, $values) {
// we don't support multiple level filtering. for now.
$bean = $this->beanManager->newBeanSafe($options->offsetGet('moduleName'));
$filter = new FilterRepository($bean->db);
return $filter->parseWhere($bean, $values);
});
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Api\V8\Param\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class Id extends BaseOption
{
/**
* @inheritdoc
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setRequired('id')
->setAllowedTypes('id', 'string')
->setAllowedValues('id', $this->validatorFactory->createClosure([
new Assert\Regex('/^(\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i')
]));
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Api\V8\Param\Options;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class LinkFieldName extends BaseOption
{
/**
* Has a dependency of bean field.
*
* @inheritdoc
* @throws \RuntimeException If relationship cannot loaded
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setRequired('linkFieldName')
->setAllowedTypes('linkFieldName', ['string'])
->setAllowedValues('linkFieldName', $this->validatorFactory->createClosure([
new Assert\NotBlank(),
new Assert\Regex([
'pattern' => ModuleName::REGEX_MODULE_NAME_PATTERN,
'match' => false,
]),
]))
->setNormalizer('linkFieldName', function (Options $options, $value) {
$bean = $options->offsetGet('sourceBean');
if (!$bean->load_relationship($value)) {
throw new \RuntimeException(
sprintf('Cannot load relationship %s for %s module', $value, $bean->getObjectName())
);
}
return $value;
});
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Api\V8\Param\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class ModuleName extends BaseOption
{
const REGEX_MODULE_NAME_PATTERN = '/^(\d|\W)|\W/';
/**
* @inheritdoc
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setRequired('moduleName')
->setAllowedTypes('moduleName', 'string')
->setAllowedValues('moduleName', $this->validatorFactory->createClosure([
new Assert\NotBlank(),
new Assert\Regex([
'pattern' => self::REGEX_MODULE_NAME_PATTERN,
'match' => false,
]),
]));
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Api\V8\Param\Options;
use Api\V8\Param\PageParams;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class Page extends BaseOption
{
const REGEX_PAGE_PATTERN = '/[^\d]/';
/**
* @inheritdoc
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setDefined('page')
->setAllowedTypes('page', 'array')
->setAllowedValues('page', $this->validatorFactory->createClosureForIterator([
new Assert\NotBlank(),
new Assert\Regex([
'pattern' => self::REGEX_PAGE_PATTERN,
'match' => false,
]),
], true))
->setNormalizer('page', function (Options $options, $values) {
$pageParams = new PageParams($this->validatorFactory, $this->beanManager);
$pageParams->configure($values);
return $pageParams;
});
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Api\V8\Param\Options;
use Api\V8\JsonApi\Repository\Sort as SortRepository;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class Sort extends BaseOption
{
const REGEX_SORT_PATTERN = '/[^\w-]/';
/**
* @inheritdoc
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setDefined('sort')
->setAllowedTypes('sort', 'string')
->setAllowedValues('sort', $this->validatorFactory->createClosure([
new Assert\NotBlank(),
new Assert\Regex([
'pattern' => self::REGEX_SORT_PATTERN,
'match' => false,
]),
], true))
->setNormalizer('sort', function (Options $options, $value) {
$bean = $this->beanManager->newBeanSafe($options->offsetGet('moduleName'));
$sort = new SortRepository();
return $sort->parseOrderBy($bean, $value);
});
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Api\V8\Param\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class Type extends BaseOption
{
/**
* @inheritdoc
*/
public function add(OptionsResolver $resolver)
{
$resolver
->setRequired('type')
->setAllowedTypes('type', 'string')
->setAllowedValues('type', $this->validatorFactory->createClosure([
new Assert\NotBlank(),
new Assert\Regex([
'pattern' => ModuleName::REGEX_MODULE_NAME_PATTERN,
'match' => false,
]),
]));
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Api\V8\Param;
use Api\V8\BeanDecorator\BeanManager;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class PageParams extends BaseParam
{
/**
* @return integer
*/
public function getSize()
{
return isset($this->parameters['size']) ? intval($this->parameters['size']) : BeanManager::DEFAULT_ALL_RECORDS;
}
/**
* @return integer
*/
public function getNumber()
{
return isset($this->parameters['number']) ? intval($this->parameters['number']) : BeanManager::DEFAULT_OFFSET;
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$resolver
->setDefined('size')
->setAllowedTypes('size', 'string')
->setAllowedValues('size', $this->validatorFactory->createClosure([
new Assert\GreaterThan(0),
]));
$resolver
->setDefined('number')
->setAllowedTypes('number', 'string')
->setAllowedValues('number', $this->validatorFactory->createClosure([
new Assert\GreaterThan(0),
]));
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Api\V8\Param;
use Api\V8\Param\Options as ParamOption;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UpdateModuleDataParams extends BaseParam
{
/**
* @return string
*/
public function getType()
{
return $this->parameters['type'];
}
/**
* @return string
*/
public function getId()
{
return $this->parameters['id'];
}
/**
* @return array
*/
public function getAttributes()
{
return isset($this->parameters['attributes']) ? $this->parameters['attributes'] : [];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
$this->setOptions(
$resolver,
[
ParamOption\Type::class,
ParamOption\Id::class,
ParamOption\Attributes::class,
]
);
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Api\V8\Param;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class UpdateModuleParams extends BaseParam
{
/**
* @return CreateModuleDataParams
*/
public function getData()
{
return $this->parameters['data'];
}
/**
* @inheritdoc
*/
protected function configureParameters(OptionsResolver $resolver)
{
// need to make this more simple
$resolver
->setRequired('data')
->setAllowedTypes('data', 'array')
->setAllowedValues('data', $this->validatorFactory->createClosureForIterator([
new Assert\NotBlank(),
]))
->setNormalizer('data', function (Options $options, $values) {
$dataParams = new UpdateModuleDataParams($this->validatorFactory, $this->beanManager);
$dataParams->configure($values);
return $dataParams;
});
}
}

View file

@ -0,0 +1,160 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Service;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\JsonApi\Helper\AttributeObjectHelper;
use Api\V8\JsonApi\Helper\PaginationObjectHelper;
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\Param\ListViewSearchParams;
use JsonSerializable;
use SearchForm;
use SuiteCRM\LangText;
use ListViewFacade;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
include_once __DIR__ . '/../../../include/SearchForm/SearchForm2.php';
include_once __DIR__ . '/../../../include/ListView/ListViewFacade.php';
/**
* ListViewSearchService
*
* @author gyula
*/
class ListViewSearchService
{
/**
* @var BeanManager
*/
private $beanManager;
/**
* @param BeanManager $beanManager
* @param AttributeObjectHelper $attributeHelper
* @param RelationshipObjectHelper $relationshipHelper
* @param PaginationObjectHelper $paginationHelper
*/
public function __construct(
BeanManager $beanManager
) {
$this->beanManager = $beanManager;
}
/**
*
* @param LangText $trans
* @param array $data
* @param string $part
* @param string $valueKey
* @param array $displayColumns
* @return array
*/
protected function getDataTranslated($trans, $data, $part, $valueKey, $displayColumns)
{
foreach ($data[$part] as $key => $value) {
$text = null;
if (isset($value[$valueKey])) {
$text = $value[$valueKey];
} elseif (isset($value['name']) && isset($displayColumns[strtoupper($value['name'])]['label'])) {
$text = $displayColumns[strtoupper($value['name'])]['label'];
} else {
\LoggerManager::getLogger()->warn("Not found translation text key for search defs for selected module field: $key");
}
$label = $text ? $trans->getText($text) : $text;
$data[$part][$key][$valueKey] = $label;
}
return $data;
}
/**
* @param ListViewSearchParams $params
*
* @return JsonSerializable
*/
public function getListViewSearchDefs(ListViewSearchParams $params)
{
// retrieving search defs
$moduleName = $params->getModuleName();
$searchDefs = SearchForm::retrieveSearchDefs($moduleName);
// get list view defs
$displayColumns = ListViewFacade::getDisplayColumns($moduleName);
// simplified data struct
$data = [
'module' => $moduleName,
'templateMeta' => $searchDefs['searchdefs'][$moduleName]['templateMeta'],
'basic' => array_values($searchDefs['searchdefs'][$moduleName]['layout']['basic_search']),
'advanced' => array_values($searchDefs['searchdefs'][$moduleName]['layout']['advanced_search']),
'fields' => $searchDefs['searchFields'][$moduleName]
];
// translations
$trans = new LangText(null, null, LangText::USING_ALL_STRINGS, true, false, $moduleName);
$data = $this->getDataTranslated($trans, $data, 'basic', 'label', $displayColumns);
$data = $this->getDataTranslated($trans, $data, 'advanced', 'label', $displayColumns);
$data = $this->getDataTranslated($trans, $data, 'fields', 'vname', $displayColumns);
// generate response
$dataResponse = new DataResponse('SearchDefs', null);
$attributeResponse = new AttributeResponse($data);
$dataResponse->setAttributes($attributeResponse);
$response = new DocumentResponse();
$response->setData($dataResponse);
return $response;
}
}

View file

@ -0,0 +1,150 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Service;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\JsonApi\Helper\AttributeObjectHelper;
use Api\V8\JsonApi\Helper\PaginationObjectHelper;
use Api\V8\JsonApi\Helper\RelationshipObjectHelper;
use Api\V8\JsonApi\Response\AttributeResponse;
use Api\V8\Param\ListViewColumnsParams;
use ListViewFacade;
use SuiteCRM\LangText;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
include_once __DIR__ . '/../../../include/ListView/ListViewFacade.php';
/**
* ListViewService
*
* @author gyula
*/
class ListViewService
{
/**
* an exact match to ListViewColumnInterface struct of Angular front-end
*
* @var array
*/
private static $listViewColumnInterface = [
'fieldName' => '',
'width' => '',
'label' => '',
'link' => false,
'default' => false,
'module' => '',
'id' => '',
'sortable' => false,
'customCode' => '', // deprecated from legacy (using only on PHP front-end)
];
/**
* @var BeanManager
*/
private $beanManager;
/**
* @var AttributeObjectHelper
*/
private $attributeHelper;
/**
* @var RelationshipObjectHelper
*/
private $relationshipHelper;
/**
* @var PaginationObjectHelper
*/
private $paginationHelper;
/**
* @param BeanManager $beanManager
* @param AttributeObjectHelper $attributeHelper
* @param RelationshipObjectHelper $relationshipHelper
* @param PaginationObjectHelper $paginationHelper
*/
public function __construct(
BeanManager $beanManager,
AttributeObjectHelper $attributeHelper,
RelationshipObjectHelper $relationshipHelper,
PaginationObjectHelper $paginationHelper
) {
$this->beanManager = $beanManager;
$this->attributeHelper = $attributeHelper;
$this->relationshipHelper = $relationshipHelper;
$this->paginationHelper = $paginationHelper;
}
/**
* @param ListViewColumnsParams $params
*
* @return JsonSerializable
*/
public function getListViewDefs(ListViewColumnsParams $params)
{
$moduleName = $params->getModuleName();
/** @var SugarBean */
$bean = \BeanFactory::getBean($moduleName);
$text = new LangText(null, null, LangText::USING_ALL_STRINGS, true, false, $moduleName);
$displayColumns = ListViewFacade::getDisplayColumns($moduleName);
$data = [];
foreach ($displayColumns as $key => $column) {
$column = array_merge(self::$listViewColumnInterface, $column);
$column['fieldName'] = $key; // get the vardef instead this "intuitive fieldName"
$translated = $text->getText($column['label']);
if (!$translated) {
$translated = $text->getText($bean->field_name_map[strtolower($key)]['vname']);
}
$column['label'] = $translated ? $translated : $column['label'];
// TODO: validate the column name (for e.g label and name should be requered etc...) also check the ListViewColumnInterface keys are match..
$data[] = $column;
}
$response = new AttributeResponse($data);
return $response;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Api\V8\Service;
use Api\V8\BeanDecorator\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
* @throws \InvalidArgumentException When access token is not found.
*/
public function logout($accessToken)
{
// same logic in Access and Refresh token repository, refactor this later
$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,250 @@
<?php
namespace Api\V8\Service;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\JsonApi\Helper\AttributeObjectHelper;
use Api\V8\JsonApi\Helper\PaginationObjectHelper;
use Api\V8\JsonApi\Helper\RelationshipObjectHelper;
use Api\V8\JsonApi\Response\DataResponse;
use Api\V8\JsonApi\Response\DocumentResponse;
use Api\V8\JsonApi\Response\MetaResponse;
use Api\V8\Param\CreateModuleParams;
use Api\V8\Param\DeleteModuleParams;
use Api\V8\Param\GetModuleParams;
use Api\V8\Param\GetModulesParams;
use Api\V8\Param\UpdateModuleParams;
use Slim\Http\Request;
class ModuleService
{
/**
* @var BeanManager
*/
private $beanManager;
/**
* @var AttributeObjectHelper
*/
private $attributeHelper;
/**
* @var RelationshipObjectHelper
*/
private $relationshipHelper;
/**
* @var PaginationObjectHelper
*/
private $paginationHelper;
/**
* @param BeanManager $beanManager
* @param AttributeObjectHelper $attributeHelper
* @param RelationshipObjectHelper $relationshipHelper
* @param PaginationObjectHelper $paginationHelper
*/
public function __construct(
BeanManager $beanManager,
AttributeObjectHelper $attributeHelper,
RelationshipObjectHelper $relationshipHelper,
PaginationObjectHelper $paginationHelper
) {
$this->beanManager = $beanManager;
$this->attributeHelper = $attributeHelper;
$this->relationshipHelper = $relationshipHelper;
$this->paginationHelper = $paginationHelper;
}
/**
* @param GetModuleParams $params
* @param string $path
*
* @return DocumentResponse
*/
public function getRecord(GetModuleParams $params, $path)
{
$fields = $params->getFields();
$bean = $this->beanManager->getBeanSafe(
$params->getModuleName(),
$params->getId()
);
$dataResponse = $this->getDataResponse($bean, $fields, $path);
$response = new DocumentResponse();
$response->setData($dataResponse);
return $response;
}
/**
* @param GetModulesParams $params
* @param Request $request
*
* @return DocumentResponse
*/
public function getRecords(GetModulesParams $params, Request $request)
{
// this whole method should split into separated classes later
$module = $params->getModuleName();
$orderBy = $params->getSort();
$where = $params->getFilter();
$fields = $params->getFields();
$size = $params->getPage()->getSize();
$number = $params->getPage()->getNumber();
// negative numbers are validated in params
$offset = $number !== 0 ? ($number - 1) * $size : $number;
$realRowCount = $this->beanManager->countRecords($module, $where);
$limit = $size === BeanManager::DEFAULT_ALL_RECORDS ? BeanManager::DEFAULT_LIMIT : $size;
$beanListResponse = $this->beanManager->getList($module)
->orderBy($orderBy)
->where($where)
->offset($offset)
->limit($limit)
->max($size)
->fetch();
$data = [];
foreach ($beanListResponse->getBeans() as $bean) {
$dataResponse = $this->getDataResponse(
$bean,
$fields,
$request->getUri()->getPath() . '/' . $bean->id
);
$data[] = $dataResponse;
}
$response = new DocumentResponse();
$response->setData($data);
// pagination
if ($data && $limit !== BeanManager::DEFAULT_LIMIT) {
$totalPages = ceil($realRowCount / $size);
$paginationMeta = $this->paginationHelper->getPaginationMeta($totalPages, count($data));
$paginationLinks = $this->paginationHelper->getPaginationLinks($request, $totalPages, $number);
$response->setMeta($paginationMeta);
$response->setLinks($paginationLinks);
}
return $response;
}
/**
* @param CreateModuleParams $params
* @param Request $request
*
* @return DocumentResponse
* @throws \InvalidArgumentException When bean is already exist.
*/
public function createRecord(CreateModuleParams $params, Request $request)
{
$module = $params->getData()->getType();
$id = $params->getData()->getId();
$attributes = $params->getData()->getAttributes();
if ($id !== null && $this->beanManager->getBean($module, $id, [], false) instanceof \SugarBean) {
throw new \InvalidArgumentException(sprintf(
'Bean %s with id %s is already exist',
$module,
$id
));
}
$bean = $this->beanManager->newBeanSafe($module);
if ($id !== null) {
$bean->id = $id;
$bean->new_with_id = true;
}
foreach ($attributes as $property => $value) {
$bean->$property = $value;
}
$bean->save();
$dataResponse = $this->getDataResponse(
$bean,
null,
$request->getUri()->getPath() . '/' . $bean->id
);
$response = new DocumentResponse();
$response->setData($dataResponse);
return $response;
}
/**
* @param UpdateModuleParams $params
* @param Request $request
*
* @return DocumentResponse
*/
public function updateRecord(UpdateModuleParams $params, Request $request)
{
$module = $params->getData()->getType();
$id = $params->getData()->getId();
$attributes = $params->getData()->getAttributes();
$bean = $this->beanManager->getBeanSafe($module, $id);
foreach ($attributes as $property => $value) {
$bean->$property = $value;
}
$bean->save();
$dataResponse = $this->getDataResponse(
$bean,
null,
$request->getUri()->getPath() . '/' . $bean->id
);
$response = new DocumentResponse();
$response->setData($dataResponse);
return $response;
}
/**
* @param DeleteModuleParams $params
*
* @return DocumentResponse
*/
public function deleteRecord(DeleteModuleParams $params)
{
$bean = $this->beanManager->getBeanSafe(
$params->getModuleName(),
$params->getId()
);
$bean->mark_deleted($bean->id);
$response = new DocumentResponse();
$response->setMeta(
new MetaResponse(['message' => sprintf('Record with id %s is deleted', $bean->id)])
);
return $response;
}
/**
* @param \SugarBean $bean
* @param array|null $fields
* @param string|null $path
*
* @return DataResponse
*/
public function getDataResponse(\SugarBean $bean, $fields = null, $path = null)
{
// this will be split into separated classed later
$dataResponse = new DataResponse($bean->getObjectName(), $bean->id);
$dataResponse->setAttributes($this->attributeHelper->getAttributes($bean, $fields));
$dataResponse->setRelationships($this->relationshipHelper->getRelationships($bean, $path));
return $dataResponse;
}
}

View file

@ -0,0 +1,150 @@
<?php
namespace Api\V8\Service;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\JsonApi\Helper\AttributeObjectHelper;
use Api\V8\JsonApi\Response\DataResponse;
use Api\V8\JsonApi\Response\DocumentResponse;
use Api\V8\JsonApi\Response\LinksResponse;
use Api\V8\JsonApi\Response\MetaResponse;
use Api\V8\Param\CreateRelationshipParams;
use Api\V8\Param\DeleteRelationshipParams;
use Api\V8\Param\GetRelationshipParams;
class RelationshipService
{
/**
* @var BeanManager
*/
private $beanManager;
/**
* @var AttributeObjectHelper
*/
private $attributeHelper;
/**
* @param BeanManager $beanManager
* @param AttributeObjectHelper $attributeHelper
*/
public function __construct(BeanManager $beanManager, AttributeObjectHelper $attributeHelper)
{
$this->beanManager = $beanManager;
$this->attributeHelper = $attributeHelper;
}
/**
* @param GetRelationshipParams $params
*
* @return DocumentResponse
*/
public function getRelationship(GetRelationshipParams $params)
{
$sourceBean = $params->getSourceBean();
$linkFieldName = $params->getLinkedFieldName();
$relatedBeans = $sourceBean->get_linked_beans($linkFieldName);
$response = new DocumentResponse();
if (!$relatedBeans) {
$response->setMeta(new MetaResponse(
[
'message' => sprintf(
'There is no relationship set in %s module with %s link field',
$sourceBean->getObjectName(),
$linkFieldName
)
]
));
} else {
$data = [];
/** @var \SugarBean $relatedBean */
foreach ($relatedBeans as $relatedBean) {
$linkResponse = new LinksResponse();
$linkResponse->setSelf(sprintf('/V8/module/%s/%s', $relatedBean->getObjectName(), $relatedBean->id));
$dataResponse = new DataResponse($relatedBean->getObjectName(), $relatedBean->id);
$dataResponse->setLinks($linkResponse);
$data[] = $dataResponse;
}
$response->setData($data);
}
return $response;
}
/**
* @param CreateRelationshipParams $params
*
* @return DocumentResponse
*/
public function createRelationship(CreateRelationshipParams $params)
{
$sourceBean = $params->getSourceBean();
$relatedBean = $params->getRelatedBean();
$linkFieldName = $this->beanManager->getLinkedFieldName($sourceBean, $relatedBean);
$this->beanManager->createRelationshipSafe($sourceBean, $relatedBean, $linkFieldName);
$response = new DocumentResponse();
$response->setMeta(new MetaResponse(
[
'message' => sprintf(
'%s with id %s has been added to %s with id %s',
$relatedBean->getObjectName(),
$relatedBean->id,
$sourceBean->getObjectName(),
$sourceBean->id
)
]
));
return $response;
}
/**
* @param DeleteRelationshipParams $params
*
* @return DocumentResponse
* @throws \DomainException When the source module is not related to the target module.
*/
public function deleteRelationship(DeleteRelationshipParams $params)
{
$sourceBean = $params->getSourceBean();
$linkFieldName = $params->getLinkedFieldName();
$relatedBeans = $sourceBean->get_linked_beans($linkFieldName);
$relatedBeanId = $params->getRelatedBeanId();
$relatedBean = array_filter($relatedBeans, function ($bean) use ($relatedBeanId) {
return $bean->id === $relatedBeanId;
});
if (!$relatedBean) {
throw new \DomainException(
sprintf(
'Module with %s id is not related to %s',
$relatedBeanId,
$sourceBean->getObjectName()
)
);
}
$relatedBean = array_shift($relatedBean);
$this->beanManager->deleteRelationshipSafe($sourceBean, $relatedBean, $linkFieldName);
$response = new DocumentResponse();
$response->setMeta(new MetaResponse(
[
'message' => sprintf(
'Relationship has been deleted between %s with id %s and %s with id %s',
$sourceBean->getObjectName(),
$sourceBean->id,
$relatedBean->getObjectName(),
$relatedBean->id
)
]
));
return $response;
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Service;
use Api\V8\BeanDecorator\BeanManager;
use Api\V8\JsonApi\Response\AttributeResponse;
use Api\V8\JsonApi\Response\DataResponse;
use Api\V8\JsonApi\Response\DocumentResponse;
use Api\V8\Param\GetUserPreferencesParams;
use DBManagerFactory;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
/**
* UserPreferencesService
*
* @author gyula
*/
class UserPreferencesService
{
/**
* @var BeanManager
*/
private $beanManager;
/**
* @param BeanManager $beanManager
*/
public function __construct(
BeanManager $beanManager
) {
$this->beanManager = $beanManager;
}
/**
*
* @param GetUserPreferencesParams $params
* @return DocumentResponse
*/
public function getUserPreferences(GetUserPreferencesParams $params)
{
// needs to determinate the user preferences
$user = $this->beanManager->getBeanSafe('Users', $params->getUserId());
$db = DBManagerFactory::getInstance();
$result = $db->query("SELECT contents, category FROM user_preferences WHERE assigned_user_id='$user->id' AND deleted = 0", false, 'Failed to load user preferences');
$preferences = [];
while ($row = $db->fetchByAssoc($result)) {
$category = $row['category'];
$preferences[$category] = unserialize(base64_decode($row['contents']));
}
$dataResponse = new DataResponse('UserPreference', $params->getUserId());
$attributeResponse = new AttributeResponse($preferences);
$dataResponse->setAttributes($attributeResponse);
$response = new DocumentResponse();
$response->setData($dataResponse);
return $response;
}
}

View file

@ -0,0 +1,118 @@
<?php
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
namespace Api\V8\Service;
use Api\V8\BeanDecorator\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 Slim\Http\Request;
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
include_once __DIR__ . '/../../../include/ListView/ListViewFacade.php';
/**
* UserService
*
* @author gyula
*/
class UserService
{
/**
* @var BeanManager
*/
private $beanManager;
/**
* @var AttributeObjectHelper
*/
private $attributeHelper;
/**
* @var RelationshipObjectHelper
*/
private $relationshipHelper;
/**
* @param BeanManager $beanManager
*/
public function __construct(
BeanManager $beanManager,
AttributeObjectHelper $attributeHelper,
RelationshipObjectHelper $relationshipHelper
) {
$this->beanManager = $beanManager;
$this->attributeHelper = $attributeHelper;
$this->relationshipHelper = $relationshipHelper;
}
/**
*
* @param Request $request
* @return DocumentResponse
*/
public function getCurrentUser(Request $request)
{
// needs to determinate the curent user without globals
$oauthClientId = $request->getAttribute('oauth_client_id');
$oauthClient = $this->beanManager->getBeanSafe('OAuth2Clients', $oauthClientId);
$currentUser = $this->beanManager->getBeanSafe('Users', $oauthClient->assigned_user_id);
$currentUserData = $currentUser->toArray();
unset($currentUserData['user_hash']);
$dataResponse = new DataResponse($currentUser->getObjectName(), $currentUser->id);
$attributeResponse = new AttributeResponse($currentUserData);
$dataResponse->setAttributes($attributeResponse);
$dataResponse->setRelationships($this->relationshipHelper->getRelationships($currentUser, $request->getUri()->getPath()));
$response = new DocumentResponse();
$response->setData($dataResponse);
return $response;
}
}

View file

@ -0,0 +1,936 @@
{
"info": {
"name": "SalesAgility",
"_postman_id": "9977217b-9e4a-b8fe-f32d-a59c29424939",
"description": "",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "SuiteCRM/V8",
"description": "",
"item": [
{
"name": "Get module",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjEyZmEzOWU5NGE1OWI1ZTkwMjBmMjg3NjZjYWM1MGRhNGJlODQ3MzE1ZmM0NjNkZTU5ZDE4Mjk2M2VlMDg3ZGQ3ZTU1NmYwMjMwMmZjMDZmIn0.eyJhdWQiOiJjOTlmZDM2Ny1kY2QwLTU4NDAtN2YwMS01YjIyNjIzMzNhYjIiLCJqdGkiOiIxMmZhMzllOTRhNTliNWU5MDIwZjI4NzY2Y2FjNTBkYTRiZTg0NzMxNWZjNDYzZGU1OWQxODI5NjNlZTA4N2RkN2U1NTZmMDIzMDJmYzA2ZiIsImlhdCI6MTUyOTA1NTgzOSwibmJmIjoxNTI5MDU1ODM5LCJleHAiOjE1MjkwNTk0MzksInN1YiI6IjEiLCJzY29wZXMiOltdfQ.NE2EPG-T1S58dkVAT4iK5TUE9s1Z_LrpzJK6YfLm8CCoAkzeRgdd1cYK63V2YspZuZENdat4QPXZU7aMSGjDguiKMiyCkIXmiB71zSUNfU-uDlPL8MUBepPc2cl72eahxJ-Z8ssbGxT8mwil9lakCaGn2K-OWl25jPtRWdeN2UC7dqVFLdbmPbThzxE3OkF1xPdUnCCbJNPyX7CZzsjV8w8Ap2k8V9pDtDNLI4BELFx17b2zKPB7gxcqNQyYZMkHIIu2N6QtpjuFXjS5Em1VgW5_SYznUVF_httQ0-IbvLGYVGzOT71dYNKMdbU0R2hNFyy3SrhVSM3OIA8iEq1SZA",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "GET",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\n}"
},
"url": {
"raw": "{{suitecrm.url}}/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493?fields[Accounts]=name,account_type",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module",
"Accounts",
"11a71596-83e7-624d-c792-5ab9006dd493"
],
"query": [
{
"key": "fields[Accounts]",
"value": "name,account_type",
"equals": true
}
]
},
"description": null
},
"response": []
},
{
"name": "Get modules",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImFiNGUyYzkyMzIxZjQ5OTgwNDMyOGNmMTFjMjdiOTk2MzBiMjE0NGE5M2YxN2FhZmI4MTM4ZDE4ZWY0OWRkYWRkNGIwOGVjNDVhOTg3NmNmIn0.eyJhdWQiOiJiMTNhMzlmOC0xYzI0LWM1ZDAtYmEwZC01YWIxMjNkNmU4OTkiLCJqdGkiOiJhYjRlMmM5MjMyMWY0OTk4MDQzMjhjZjExYzI3Yjk5NjMwYjIxNDRhOTNmMTdhYWZiODEzOGQxOGVmNDlkZGFkZDRiMDhlYzQ1YTk4NzZjZiIsImlhdCI6MTUyNjU1ODQyNiwibmJmIjoxNTI2NTU4NDI2LCJleHAiOjE1MjY1NjIwMjYsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.XoR3_CT19V8vkrSC668oQen6UHGiJB_EqUArvTUj6svuYW_VFS5QP57P2NCPuy_NlcD3WmqKgSG_v2c_ye7tvEV1dnpW1dqI0yWUTtOmRICtlPzc_DQgVCPJKMU0OFyBDIdoPu7hOL3gp-D46ImMQuQhBQDYVN9AAyBwef8CeSJ_0_V7_nrryXBRisiAGnQAhuuXcGsbUpVRnssBizBQzif9-ikRoh7PQ6q3WZ6oqmZCyj_7-azQyAMs3I0VytdV_EnfTJbwLTl4r47zwiahHy72JH5oTOdItjngFB921IncXSPGWukBUflUbz-Ds7A5tPZBKm2axpKxQRV_Dt-daA",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "GET",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\n}"
},
"url": {
"raw": "{{suitecrm.url}}/V8/module/Accounts?fields[Accounts]=name,account_type&page[size]=4&page[number]=4&sort=name&filter[operator]=and&filter[account_type][eq]=Customer",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module",
"Accounts"
],
"query": [
{
"key": "fields[Accounts]",
"value": "name,account_type",
"equals": true
},
{
"key": "page[size]",
"value": "4",
"equals": true
},
{
"key": "page[number]",
"value": "4",
"equals": true
},
{
"key": "sort",
"value": "name",
"equals": true
},
{
"key": "filter[operator]",
"value": "and",
"equals": true
},
{
"key": "filter[account_type][eq]",
"value": "Customer",
"equals": true
}
]
},
"description": null
},
"response": []
},
{
"name": "Create module",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImEwYTczODU5NjZkY2FiNjM3ZTk3YzM5ZWU2MzQ1NzhiZTIxYTlkMDViNmUxMmRiMDBjN2ZiMDYyYTA5ZTRjNzgxYTFjMmM4N2RiNmRhMzgwIn0.eyJhdWQiOiJjOTlmZDM2Ny1kY2QwLTU4NDAtN2YwMS01YjIyNjIzMzNhYjIiLCJqdGkiOiJhMGE3Mzg1OTY2ZGNhYjYzN2U5N2MzOWVlNjM0NTc4YmUyMWE5ZDA1YjZlMTJkYjAwYzdmYjA2MmEwOWU0Yzc4MWExYzJjODdkYjZkYTM4MCIsImlhdCI6MTUyOTUwNjE3OCwibmJmIjoxNTI5NTA2MTc4LCJleHAiOjE1Mjk1MDk3NzgsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.sUBY05yiw6WGeeftrZOPesSSxzxvEoNSoP5oL3Q_pdrdm7Z5UTN0dnedUsL4fNSlPKzyuuFgFxlwLCk-kUTQ1LtSVe2OuMGdURxbK-hoA5K-5MEwWP-EudckJAcToTLVhr7K4iZdhK1i1_HRSFG-_9MWM3HkyB9xr2JpySdxlUvD9yxhmOMgK5BdL-m7IdeokoQUPE5GFZkbG0OA8EVJ92otD0PYE_6AbC8RIGqGgRZmIQDDe5AWTwT-6xYsHMTGpGQQUNiYRlGA_cQj3wseXZAoW13WpmHhoKOoJcI2A4uLzrOIoYzInujsTLtSUnJkxo47T7ZL7xNmbJEXRcfVvQ",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"data\": {\n \"type\": \"Accounts\",\n \"id\": \"11a71596-83e7-624d-c792-5ab9006dd493\",\n \"attributes\": {\n \"name\": \"Muhahaha\"\n }\n }\n}"
},
"url": {
"raw": "{{suitecrm.url}}/V8/module",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module"
]
},
"description": null
},
"response": []
},
{
"name": "Update module",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImEwYTczODU5NjZkY2FiNjM3ZTk3YzM5ZWU2MzQ1NzhiZTIxYTlkMDViNmUxMmRiMDBjN2ZiMDYyYTA5ZTRjNzgxYTFjMmM4N2RiNmRhMzgwIn0.eyJhdWQiOiJjOTlmZDM2Ny1kY2QwLTU4NDAtN2YwMS01YjIyNjIzMzNhYjIiLCJqdGkiOiJhMGE3Mzg1OTY2ZGNhYjYzN2U5N2MzOWVlNjM0NTc4YmUyMWE5ZDA1YjZlMTJkYjAwYzdmYjA2MmEwOWU0Yzc4MWExYzJjODdkYjZkYTM4MCIsImlhdCI6MTUyOTUwNjE3OCwibmJmIjoxNTI5NTA2MTc4LCJleHAiOjE1Mjk1MDk3NzgsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.sUBY05yiw6WGeeftrZOPesSSxzxvEoNSoP5oL3Q_pdrdm7Z5UTN0dnedUsL4fNSlPKzyuuFgFxlwLCk-kUTQ1LtSVe2OuMGdURxbK-hoA5K-5MEwWP-EudckJAcToTLVhr7K4iZdhK1i1_HRSFG-_9MWM3HkyB9xr2JpySdxlUvD9yxhmOMgK5BdL-m7IdeokoQUPE5GFZkbG0OA8EVJ92otD0PYE_6AbC8RIGqGgRZmIQDDe5AWTwT-6xYsHMTGpGQQUNiYRlGA_cQj3wseXZAoW13WpmHhoKOoJcI2A4uLzrOIoYzInujsTLtSUnJkxo47T7ZL7xNmbJEXRcfVvQ",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "PATCH",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"data\": {\n \"type\": \"Accounts\",\n \"id\": \"11a71596-83e7-624d-c792-5ab9006dd493\",\n \"attributes\": {\n \"name\": \"hapff111fff\"\n }\n }\n}"
},
"url": {
"raw": "{{suitecrm.url}}/V8/module",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module"
]
},
"description": ""
},
"response": []
},
{
"name": "Delete module",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImEwYTczODU5NjZkY2FiNjM3ZTk3YzM5ZWU2MzQ1NzhiZTIxYTlkMDViNmUxMmRiMDBjN2ZiMDYyYTA5ZTRjNzgxYTFjMmM4N2RiNmRhMzgwIn0.eyJhdWQiOiJjOTlmZDM2Ny1kY2QwLTU4NDAtN2YwMS01YjIyNjIzMzNhYjIiLCJqdGkiOiJhMGE3Mzg1OTY2ZGNhYjYzN2U5N2MzOWVlNjM0NTc4YmUyMWE5ZDA1YjZlMTJkYjAwYzdmYjA2MmEwOWU0Yzc4MWExYzJjODdkYjZkYTM4MCIsImlhdCI6MTUyOTUwNjE3OCwibmJmIjoxNTI5NTA2MTc4LCJleHAiOjE1Mjk1MDk3NzgsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.sUBY05yiw6WGeeftrZOPesSSxzxvEoNSoP5oL3Q_pdrdm7Z5UTN0dnedUsL4fNSlPKzyuuFgFxlwLCk-kUTQ1LtSVe2OuMGdURxbK-hoA5K-5MEwWP-EudckJAcToTLVhr7K4iZdhK1i1_HRSFG-_9MWM3HkyB9xr2JpySdxlUvD9yxhmOMgK5BdL-m7IdeokoQUPE5GFZkbG0OA8EVJ92otD0PYE_6AbC8RIGqGgRZmIQDDe5AWTwT-6xYsHMTGpGQQUNiYRlGA_cQj3wseXZAoW13WpmHhoKOoJcI2A4uLzrOIoYzInujsTLtSUnJkxo47T7ZL7xNmbJEXRcfVvQ",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "DELETE",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{suitecrm.url}}/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module",
"Accounts",
"11a71596-83e7-624d-c792-5ab9006dd493"
]
},
"description": ""
},
"response": []
},
{
"name": "Get relationship",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjMyZWQ4ZGEzZjQxM2QyNWI1ZGUxMWNhNGFjNWVmMWYwMmFkNDkwNzkyOGU4NjcyYTM1NWNmYTRmY2VkNWNkZWU5MjUzODVkN2I0MDU1ZmM2In0.eyJhdWQiOiJjOTlmZDM2Ny1kY2QwLTU4NDAtN2YwMS01YjIyNjIzMzNhYjIiLCJqdGkiOiIzMmVkOGRhM2Y0MTNkMjViNWRlMTFjYTRhYzVlZjFmMDJhZDQ5MDc5MjhlODY3MmEzNTVjZmE0ZmNlZDVjZGVlOTI1Mzg1ZDdiNDA1NWZjNiIsImlhdCI6MTUyOTUwMjU1MSwibmJmIjoxNTI5NTAyNTUxLCJleHAiOjE1Mjk1MDYxNTEsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.s-eQwjwvS2DA8A4GjATUnRyrTQ5MRhoDMpzcArtVWnLZT0sLWrUqDh3qtZ6U5qGcli-C609E_hUoi0wtn1SEVTMXMSIbu_ipb109LvAv9dgpOfnFdP8kOIOvQR9kkMnI0jw4AY7w0PrON_-kZjPvucBZuRv5TVguJwRSXy3_xVFMiXGI1KfyanFwpW4AT1aDmdQ-R6nl7ginGpEzphySOK8khe9FEb9rvvwtqJGbZG7khFMVw9nAzVEF4pT4ZCS774cwEhagxWUeJKs-8yYPANMJyoFxlds54InJTuxlB6P6AIddBtjMc8_aiGz1-PU8Ulsjkpp-b_KslXUtPUGy1w",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "GET",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\n}"
},
"url": {
"raw": "{{suitecrm.url}}/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493/relationships/contacts",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module",
"Accounts",
"11a71596-83e7-624d-c792-5ab9006dd493",
"relationships",
"contacts"
]
},
"description": null
},
"response": []
},
{
"name": "Create relationship",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjMyZWQ4ZGEzZjQxM2QyNWI1ZGUxMWNhNGFjNWVmMWYwMmFkNDkwNzkyOGU4NjcyYTM1NWNmYTRmY2VkNWNkZWU5MjUzODVkN2I0MDU1ZmM2In0.eyJhdWQiOiJjOTlmZDM2Ny1kY2QwLTU4NDAtN2YwMS01YjIyNjIzMzNhYjIiLCJqdGkiOiIzMmVkOGRhM2Y0MTNkMjViNWRlMTFjYTRhYzVlZjFmMDJhZDQ5MDc5MjhlODY3MmEzNTVjZmE0ZmNlZDVjZGVlOTI1Mzg1ZDdiNDA1NWZjNiIsImlhdCI6MTUyOTUwMjU1MSwibmJmIjoxNTI5NTAyNTUxLCJleHAiOjE1Mjk1MDYxNTEsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.s-eQwjwvS2DA8A4GjATUnRyrTQ5MRhoDMpzcArtVWnLZT0sLWrUqDh3qtZ6U5qGcli-C609E_hUoi0wtn1SEVTMXMSIbu_ipb109LvAv9dgpOfnFdP8kOIOvQR9kkMnI0jw4AY7w0PrON_-kZjPvucBZuRv5TVguJwRSXy3_xVFMiXGI1KfyanFwpW4AT1aDmdQ-R6nl7ginGpEzphySOK8khe9FEb9rvvwtqJGbZG7khFMVw9nAzVEF4pT4ZCS774cwEhagxWUeJKs-8yYPANMJyoFxlds54InJTuxlB6P6AIddBtjMc8_aiGz1-PU8Ulsjkpp-b_KslXUtPUGy1w",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"data\": {\n \"type\": \"Contacts\",\n \"id\": \"11806811-0b4b-fcdd-268b-5b2260e68333\"\n }\n}"
},
"url": {
"raw": "{{suitecrm.url}}/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493/relationships",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module",
"Accounts",
"11a71596-83e7-624d-c792-5ab9006dd493",
"relationships"
]
},
"description": null
},
"response": []
},
{
"name": "Delete relationship",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjE1MDhmZWY3Yzg2OTkxODliMGZlOWE2Nzk1MDcyMzhjNTM4YWM2YzlmMTI2M2JjMWJiZmVkMDdjMjRlYmY4MGU0MWM5NDdiNmUyYTFhYzRjIn0.eyJhdWQiOiJjOTlmZDM2Ny1kY2QwLTU4NDAtN2YwMS01YjIyNjIzMzNhYjIiLCJqdGkiOiIxNTA4ZmVmN2M4Njk5MTg5YjBmZTlhNjc5NTA3MjM4YzUzOGFjNmM5ZjEyNjNiYzFiYmZlZDA3YzI0ZWJmODBlNDFjOTQ3YjZlMmExYWM0YyIsImlhdCI6MTUyOTQ4ODczNywibmJmIjoxNTI5NDg4NzM3LCJleHAiOjE1Mjk0OTIzMzcsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.WYBD1Gc9ggTHjXqSxRvxmM-WaR9gdL6lJyqznnD9Vvm6xNll4SKe54wM9rR_PZQjMtNdCATCtewLcnB5EzGsS7RqVZcBDhFKVvt3LC0oScPpnfw20qTeAZECnsuOs9RrLD_JAV4Lk-8WhUF5PZUl9n_UK1V8Y9ll8DMQryiBj1XrN54_LEPKeQbnHOKTpqptXjxz6rVZ9oc0JjJzwG2x3CUK9J8sp0RhQjSx_G_SJrAjVkM0yDKPaYg2Cn5OyvRRMjFhTwNxW6gUjnK9WgP65d5jNwXhmcNsIeb_iDCl5aYaqDPf3V0mARoS_BFjhiizu5QSwlfH16whhKx_ZwVpKQ",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "DELETE",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "{{suitecrm.url}}/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493/relationships/contacts/11806811-0b4b-fcdd-268b-5b2260e68333",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"module",
"Accounts",
"11a71596-83e7-624d-c792-5ab9006dd493",
"relationships",
"contacts",
"11806811-0b4b-fcdd-268b-5b2260e68333"
]
},
"description": null
},
"response": []
},
{
"name": "Logout",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "accessToken",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjNhZDE1ZDQzZjFlMmRhNDhhZGMzMmU1OWFlNWE2NmRjYzI4ZjljZjFmZmJhMzdkMjQ4YzExOWVmOGI1ZmI4MWM1Y2MxOTdkOWQ4ZmEwYzliIn0.eyJhdWQiOiJiMTNhMzlmOC0xYzI0LWM1ZDAtYmEwZC01YWIxMjNkNmU4OTkiLCJqdGkiOiIzYWQxNWQ0M2YxZTJkYTQ4YWRjMzJlNTlhZTVhNjZkY2MyOGY5Y2YxZmZiYTM3ZDI0OGMxMTllZjhiNWZiODFjNWNjMTk3ZDlkOGZhMGM5YiIsImlhdCI6MTUyNTE2MDEzNSwibmJmIjoxNTI1MTYwMTM1LCJleHAiOjE1MjUyMDMzMzUsInN1YiI6IjEiLCJzY29wZXMiOltdfQ.Ypei77VrFLiY-TLI4fCUWyDVGpJ_H9OSdHmZKmReRpKKVuhltmTgyYjYDU96yWF9uBrjsthF7_87bCfTk-wIYelSqH_v-02-TlQru_qyNd9xkUxmm3-7v03FI3Fw9gyj0zruPtHBi6V8x0G1ZSYAqsuAIygIPpZH10TrNKPYIFeo0Lxwg3oGkpH9YfXGJSQkliFv--euiB3C2sJDE9Djk-xxA-48xwZvzn-249-ox7FI6qU2OIPNDlnVjx0l8N9KVjFPsz79U40T259QvwHZiFiubUE4pWjs6u74KBr-z6Rls0d3ndqi8JYA5ZLYjEQq8txmrqwjBISn1nZqBsGbiQ",
"type": "string"
},
{
"key": "tokenType",
"value": "Bearer",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
},
{
"key": "callBackUrl",
"type": "any"
},
{
"key": "authUrl",
"type": "any"
},
{
"key": "accessTokenUrl",
"type": "any"
},
{
"key": "clientId",
"type": "any"
},
{
"key": "clientSecret",
"type": "any"
},
{
"key": "clientAuth",
"type": "any"
},
{
"key": "grantType",
"type": "any"
},
{
"key": "scope",
"type": "any"
},
{
"key": "username",
"type": "any"
},
{
"key": "password",
"type": "any"
},
{
"key": "redirectUri",
"type": "any"
},
{
"key": "refreshToken",
"type": "any"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\n}"
},
"url": {
"raw": "{{suitecrm.url}}/V8/logout",
"host": [
"{{suitecrm.url}}"
],
"path": [
"V8",
"logout"
]
},
"description": ""
},
"response": []
}
]
}
]
}

View file

@ -0,0 +1,488 @@
{
"openapi": "3.0.0",
"info": {
"title": "SalesAgility REST API",
"version": "8.1",
"contact": {
"name": "Support",
"url": "https://suitecrm.com/forum"
},
"license": {
"name": "GNU AFFERO GENERAL PUBLIC LICENSE VERSION 3",
"url": "https://github.com/salesagility/SuiteCRM/blob/master/LICENSE.txt"
}
},
"servers": [
{
"url": "http://localhost/forkedSuite/Api/V8"
}
],
"paths": {
"/module/{moduleName}/{id}": {
"get": {
"tags": [
"Module"
],
"description": "Returns a bean with the specific ID",
"parameters": [
{
"name": "moduleName",
"in": "path",
"description": "Name of the module",
"required": true,
"schema": {
"type": "string"
},
"example": "Contacts"
},
{
"name": "id",
"in": "path",
"description": "ID of the module",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"example": "b13a39f8-1c24-c5d0-ba0d-5ab123d6e899"
},
{
"name": "fields[Contacts]",
"in": "query",
"description": "Filtering attributes of the bean",
"schema": {
"type": "string"
},
"example": "name,account_type"
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
},
"delete": {
"tags": [
"Module"
],
"description": "Delete a bean with specific ID",
"parameters": [
{
"name": "moduleName",
"in": "path",
"description": "Name of the module",
"required": true,
"schema": {
"type": "string"
},
"example": "Contacts"
},
{
"name": "id",
"in": "path",
"description": "ID of the module",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"example": "b13a39f8-1c24-c5d0-ba0d-5ab123d6e899"
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
}
},
"/module/{module}": {
"get": {
"tags": [
"Module"
],
"description": "Returns a collections of beans",
"parameters": [
{
"name": "module",
"in": "path",
"description": "Name of the module",
"required": true,
"schema": {
"type": "string"
},
"example": "Contacts"
},
{
"name": "fields[Contacts]",
"in": "query",
"description": "Filtering attributes of each bean",
"schema": {
"type": "string"
},
"example": "name,account_type"
},
{
"name": "page[size]",
"in": "query",
"description": "Number of beans showed in a page",
"schema": {
"type": "integer"
},
"example": "4"
},
{
"name": "page[number]",
"in": "query",
"description": "Number of a page",
"schema": {
"type": "integer"
},
"example": "4"
},
{
"name": "sort",
"in": "query",
"description": "Sorting the bean list based on this parameter. Ascending by default, but if sort is prefixed with a minus (U+002D HYPHEN-MINUS, '-'), sort will be descending",
"schema": {
"type": "string"
},
"example": "-name"
},
{
"name": "filter[operator]",
"in": "query",
"description": "Filtering the bean collection and using it between two or more conditions as logical operator. Only one level conditions are supported so far. Supported operators: AND, OR",
"schema": {
"type": "string"
},
"example": "AND"
},
{
"name": "filter[name][eq]",
"in": "query",
"description": "Filtering the bean collections by conditions. The [name] is the bean's property, the [eq] is a comparison operator. Supported operators: EQ, NEQ, GT, GTE, LT, LTE",
"schema": {
"type": "string"
},
"example": "John Doe"
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
}
},
"/module": {
"post": {
"tags": [
"Module"
],
"requestBody": {
"description": "Create a module record. If ID is not set, it will be created automatically. Attributes is optional, if the new bean will be set with certain, valid properties",
"content": {
"application/vnd.api+json": {
"schema": {
"example": {
"data": {
"type": "Accounts",
"id": "86ee02b3-96d2-47b3-bd6d-9e1035daff3a",
"attributes": {
"name": "Account name"
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "CREATED"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
},
"patch": {
"tags": [
"Module"
],
"requestBody": {
"description": "Update a module record. Type and ID are required, attributes have to be valid",
"content": {
"application/vnd.api+json": {
"schema": {
"example": {
"data": {
"type": "Accounts",
"id": "86ee02b3-96d2-47b3-bd6d-9e1035daff3a",
"attributes": {
"name": "Another account name"
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
}
},
"/module/{moduleName}/{id}/relationships/{relationship}": {
"get": {
"tags": [
"Relationship"
],
"description": "Get relationship of a bean",
"parameters": [
{
"name": "moduleName",
"in": "path",
"description": "Name of the module",
"required": true,
"schema": {
"type": "string"
},
"example": "Accounts"
},
{
"name": "id",
"in": "path",
"description": "ID of the module",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"example": "11a71596-83e7-624d-c792-5ab9006dd493"
},
{
"name": "relationship",
"in": "path",
"description": "The name of the relationship related to the module",
"required": true,
"schema": {
"type": "string"
},
"example": "contacts"
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
}
},
"/module/{moduleName}/{id}/relationships": {
"post": {
"tags": [
"Relationship"
],
"parameters": [
{
"name": "moduleName",
"in": "path",
"description": "Name of the module",
"required": true,
"schema": {
"type": "string"
},
"example": "Accounts"
},
{
"name": "id",
"in": "path",
"description": "ID of the module",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"example": "11a71596-83e7-624d-c792-5ab9006dd493"
}
],
"requestBody": {
"description": "Add relationship to a module. The type is the name of the relationship",
"content": {
"application/vnd.api+json": {
"schema": {
"example": {
"data": {
"type": "contacts"
}
}
}
}
}
},
"responses": {
"201": {
"description": "CREATED"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
}
},
"/module/{moduleName}/{id}/relationships/{relationship}/{relatedBeanId}": {
"delete": {
"tags": [
"Relationship"
],
"description": "Delete relationship between 2 modules",
"parameters": [
{
"name": "moduleName",
"in": "path",
"description": "Name of the module",
"required": true,
"schema": {
"type": "string"
},
"example": "Contacts"
},
{
"name": "id",
"in": "path",
"description": "ID of the module",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"example": "b13a39f8-1c24-c5d0-ba0d-5ab123d6e899"
},
{
"name": "relationship",
"in": "path",
"description": "The name of the relationship related to the module",
"required": true,
"schema": {
"type": "string"
},
"example": "contacts"
},
{
"name": "relatedBeanId",
"in": "path",
"description": "ID of the related module",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"example": "11806811-0b4b-fcdd-268b-5b2260e68333"
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "BAD REQUEST"
}
},
"security": [
{
"oauth2": []
}
]
}
},
"/logout": {
"post": {
"tags": [
"Logout"
],
"description": "Logging out",
"responses": {
"200": {
"description": "OK"
},
"401": {
"description": "UNAUTHORIZED"
}
},
"security": [
{
"oauth2": []
}
]
}
}
},
"components": {
"securitySchemes": {
"oauth2": {
"type": "oauth2",
"flows": {
"password": {
"tokenUrl": "http://localhost/forkedSuite/Api/access_token",
"scopes": {}
}
}
}
}
}
}

4
Api/index.php Normal file
View file

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

View file

@ -436,14 +436,11 @@ class ModuleScanner
echo "'''Default Extensions'''<br>";
foreach ($this->validExt as $b) {
echo '#' . $b . '<br>';
}
echo "'''Default Black Listed Functions'''<br>";
foreach ($this->blackList as $b) {
echo '#' . $b . '<br>';
}
}
/**

View file

@ -24,8 +24,10 @@
"psr/log": "^1.0",
"league/oauth2-server": "^5.1",
"justinrainbow/json-schema": "^5.2",
"onelogin/php-saml": "3.0.0.x-dev as 3.0.0",
"onelogin/php-saml": "^3.0.0",
"google/recaptcha": "^1.1",
"symfony/options-resolver": "^3.4",
"symfony/validator": "^3.4",
"ezyang/htmlpurifier": "^4.10"
},
"require-dev": {
@ -36,7 +38,9 @@
"jeroendesloovere/vcard": "v1.5",
"browserstack/browserstack-local": "dev-master",
"consolidation/robo": "^1.0.0",
"mockery/mockery": "^0.9.9"
"mockery/mockery": "^0.9.9",
"symfony/yaml": "^3.4",
"flow/jsonpath": "^0.3"
},
"scripts": {},
"prefer-stable": true,
@ -51,7 +55,10 @@
"SuiteCRM\\Custom\\": [
"custom/lib"
]
}
},
"classmap": [
"Api/"
]
},
"extra": {}
}

719
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -195,8 +195,7 @@ class One2MBeanRelationship extends One2MRelationship
LoggerManager::getLogger()->warn('Incorrect linked relationship rhs ID: ' . get_class($link->getFocus()) . '::$' . $rhsID . ' is undefined');
}
if (!empty($id))
{
if (!empty($id)) {
$rows[$id] = array('id' => $id);
}
} else { //If the link is LHS, we need to query to get the full list and load all the beans.

View file

@ -63,14 +63,14 @@ class JSON
/**
* JSON encode a string
*
* @param array $string
* @param array $array
* @param bool $addSecurityEnvelope defaults to false
* @param bool $encodeSpecial
* @return string
*/
public static function encode($string, $addSecurityEnvelope = false, $encodeSpecial = false)
public static function encode($array, $addSecurityEnvelope = false, $encodeSpecial = false)
{
$encodedString = json_encode($string);
$encodedString = json_encode($array);
if ($encodeSpecial) {
$charMap = array('<' => '\u003C', '>' => '\u003E', "'" => '\u0027', '&' => '\u0026');

Some files were not shown because too many files have changed in this diff Show more