lionixevolve / graphqlsuitecrm
SuiteCRM / SugarCRM CE 的 GraphQL 支持
Requires
- gargron/fileupload: ~1.4.0
- monolog/monolog: ^1.23
- slim/slim: ~3.12
- youshido/graphql: ^v1.5
- dev-master
- v0.13.4
- v0.13.3
- v0.13.2
- v0.13.1
- v0.13
- v0.12.12
- v0.12.11
- v0.12.10
- v0.12.9
- v0.12.8
- v0.12.7
- v0.12.6
- v0.12.5
- v0.12.4
- v0.12.3
- v0.12.2
- v0.12.1
- v0.12.0
- v0.11.4
- v0.11.3
- v0.11.2
- v0.11.1
- v0.11.0
- v0.10.0
- v0.9.14
- v0.9.13
- v0.9.12
- v0.9.11
- v0.9.10
- v0.9.9
- v0.9.8
- v0.9.7
- v0.9.6
- v0.9.5
- v0.9.3
- v0.9.2
- v0.9.1
- v0.9.0
This package is auto-updated.
Last update: 2024-09-09 07:39:35 UTC
README
即将推出的新版本 SuiteCRM 8 将原生支持 GraphQL,因此只有错误修复和较小的新功能!
当前代码与 SuiteCRM rest API 的工作良好。
但我已经基于一个更强大的 PHP-GraphQL 库 webonyx/graphql 开发下一个版本。它进展顺利,性能更好,我正在使用 100% V8 API 端点(使用 oauth-tokens 而不是 cookies)。
GraphQLSuiteCRM
使用 GraphQL 而不是 REST API 访问 SuiteCRM 数据。适用于生产环境。支持自定义模块和自定义 REST 端点,带或不带身份验证。可以与 SuiteCRM v8 API 集成以使用其原生身份验证。
它做了什么
此包允许使用 GraphQL 与 SuiteCRM (8.x / 10.x) /SugarCRM (6.5) 一起使用,而不是不真正 REST 的 Rest API。
安装后,将创建一个 graphql 端点 vendor/lionixevolve/graphqlsuitecrm/rest.php/graphql,如果您在 SuiteCRM 上已认证(使用 cookies),则可以对其进行查询。
端点中还有更多未记录的功能,例如获取下拉值。TODO:记录此信息
发送查询到 rest.php/graphql 后,它将使用基于 Youshido/GraphQL 库的 PHP SuiteCRM Graphql 架构来处理您的查询。
此 GraphQL 库使用 SuiteCRM/SugarCRM 实体豆(bean)进行几乎所有操作,因此您创建的每个逻辑钩子或工作流都应该按计划工作。
设置
composer require lionixevolve/graphqlsuitecrm
这将安装插件到 vendor 文件夹中,包括所有要求。
PHP 扩展 php-intl 是必需的,对于 ubuntu 安装为 sudo apt-get install php-intl
测试和用法
GraphiQL 包含在包中,要开始使用它,请在 https:///vendor/lionixevolve/graphqlsuitecrm/graphql/GraphiQL/(根据 suitecrm 实例位置调整 localhost)中打开网络浏览器。使用包含的 GraphiQL 尝试此操作
{
accounts(limit:2) {
name
}
}
它将获取每个账户模块的名称。限制为 2。您还可以使用 offset:2 进行分页。
一个高级示例 - 根据状态检索所有案例。您还可以看到一些针对 assigned_user/created_by 用户字段进行的架构更改,这可以让您检索有关用户的更多信息。
query cases( $status: String, $limit: String, $offset: String){
cases( status: $status, offset:$offset, limit: $limit, order:"case_number" ){
id
case_number
date_entered
description
status
assigned_user_details{
id
first_name
last_name
user_name
}
created_user_details {
id
first_name
last_name
user_name
}
accounts {
name
id
contacts{
id
}
}
}
}
如您所见,相关模块也使用关系的复数形式检索。Accounts/Opportunities 等。
扩展/自定义 Suitecrm Graphql 架构
库将从 graphql 文件夹加载自定义文件。
加载了两个主要文件
graphql/CustomSuiteCRMSchema.phpgraphql/CustomRest.php
第 1 个将扩展包含的架构,用于自定义模块或甚至非 SuiteCRM 相关的查询。第 2 个允许您创建自己的 REST 端点调用,我们将其用于报告生成,其中 GraphQL 结果集效率不高。(如使用 aggrid 兼容的结果集)
以下是一个扩展的完整示例。
扩展架构 -
这里我正在为尚未包含在主库中的 prospectlist 创建支持。
这是从一个有效配置中提取的
<?php
//`graphql/CustomSuiteCRMSchema.php`
require_once 'Schema/ProspectlistListType.php';
require_once 'Schema/ProspectlistprospectInputType.php';
require_once 'Schema/ProspectlistType.php';
require_once 'Schema/customAccountType.php';
use Youshido\GraphQL\Execution\ResolveInfo;
use Youshido\GraphQL\Type\Scalar\StringType;
class CustomSuiteCRMSchema
{
public function buildSchema($config)
{
$config->getQuery()->addFields([
'prospectlists' => [
'type' => new ProspectlistListType(),
'args' => argsHelper::entityArgsHelper('ProspectLists'),
'resolve' => function ($value, array $args, ResolveInfo $info) {
return $info->getReturnType()->resolve($value, $args, $info);
},
],
]);
$config->getMutation()->addFields([
'assignProspectlistprospect' => [
'type' => new ProspectlistprospectInputType(),
'args' => ['beanid' => new StringType(), 'beanname' => new StringType(), 'prospectlistid' => new StringType()],
'resolve' => function ($value, $args, $type) {
$result = ProspectlistprospectInputType::resolve($value, $args, $type);
return ["id" => $result];
},
],
]);
}
}
<?php
//Schema/ProspectlistsType.php
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Execution\ResolveInfo;
class ProspectlistType extends AbstractObjectType // extending abstract Object type
{
public function build($config) // implementing an abstract function where you build your type
{
foreach (argsHelper::entityArgsHelper('ProspectLists') as $field => $type) {
$config->addField($field, $type);
}
$config->addField('related_contacts',[
'type' => new ContactsListType(),
'args' => argsHelper::entityArgsHelper('Contacts'),
'resolve' => function ($value, array $args, ResolveInfo $info) {
if (!empty($value['related_contacts'])) {
$args['ids']=$value['related_contacts'];
return ContactsListType::resolve($value, $args, $info);
} else {
return null;
}
},
]);
$config->addField('related_accounts', [
'type' => new AccountsListType(),
'args' => argsHelper::entityArgsHelper('Accounts'),
'resolve' => function ($value, array $args, ResolveInfo $info) {
if (!empty($value['related_accounts'])) {
$args['ids']=$value['related_accounts'];
return AccountsListType::resolve($value, $args, $info);
} else {
return null;
}
},
]);
}
private function retrieveProspectlist($id, $info)
{
global $sugar_config, $current_user;
$prospectlistBean = BeanFactory::getBean('ProspectLists');
$bean = $prospectlistBean->retrieve($id);
if($info!=null){
$getFieldASTList=$info->getFieldASTList();
$queryFields=[];
foreach ($getFieldASTList as $key => $value) {
$queryFields[$value->getName()]="";
}
}
$module_arr = array();
if ($bean->id && $bean->ACLAccess('view')) {
$all_fields = $bean->column_fields;
foreach ($all_fields as $field) {
if (isset($bean->$field) && !is_object($bean->$field)) {
$bean->$field = from_html($bean->$field);
$bean->$field = preg_replace("/\r\n/", '<BR>', $bean->$field);
$bean->$field = preg_replace("/\n/", '<BR>', $bean->$field);
$module_arr[$field] = $bean->$field;
}
}
$module_arr['created_user_details'] = $module_arr['created_by'];
$module_arr['assigned_user_details'] = $module_arr['assigned_user_id'];
$module_arr['modified_user_details'] = $module_arr['modified_user_id'];
foreach ($bean->get_linked_beans('contacts', 'Contact') as $contact) {
$module_arr['related_contacts'][] = $contact->id;
}
foreach ($bean->get_linked_beans('accounts', 'Account') as $account) {
$module_arr['related_accounts'][] = $account->id;
}
foreach ($bean->get_linked_beans('opportunities', 'Opportunity') as $opportunity) {
$module_arr['related_opportunities'][] = $opportunity->id;
}
return $module_arr;
} else {
error_log(__METHOD__.'----'.__LINE__.'----'.'error resolving CallType');
return;
}
}
public function resolve($value = null, $args = [], $info = null ) // implementing resolve function
{
if (isset($args['id']) && is_array($args['id'])) {
foreach ($args as $key => $nodeId) {
if (isset($nodeId) && is_array($nodeId)) {
foreach ($nodeId as $key => $nodeIdItem) {
$resultArray[] = self::retrieveProspectlist($nodeIdItem, $info );
}
} elseif (!empty($nodeId)) {
$resultArray[] = self::retrieveProspectlist($nodeId, $info );
}
}
return $resultArray;
} elseif (!empty($args['id'])) {
return self::retrieveProspectlist($args['id'],$info);
}
}
public function getName()
{
return 'ProspectList';
}
}
<?php
// schema/ProspectlistListType.php
use Youshido\GraphQL\Type\ListType\AbstractListType;
if (!defined('sugarEntry')) {
define('sugarEntry', true);
}
require_once 'modules/Prospects/Prospect.php';
require_once 'modules/ProspectLists/ProspectList.php';
class ProspectlistListType extends AbstractListType
{
public function getItemType()
{
return new ProspectlistType();
}
public function build($config)
{
// $config
// ->addArgument('limit', TypeMap::TYPE_INT)
// ->addArgument('offset', TypeMap::TYPE_INT);
}
public function endswith($string, $test)
{
$strlen = strlen($string);
$testlen = strlen($test);
if ($testlen > $strlen) {
return false;
}
return substr_compare($string, $test, $strlen - $testlen, $testlen) === 0;
}
public function resolve($value = null, $args = [], $info = null)
{
global $current_user;
require_once 'vendor/lionixevolve/graphqlsuitecrm/graphql/Schema/ListHelper.php';
$list=ListHelper('ProspectLists',$value , $args , $info );
$resultArray = [];
if (is_array($list['list']) && !empty($list['list'])) {
if ($list['list'][0]->ACLAccess('list')) {
foreach ($list['list'] as $item) {
$resultArray[] = ProspectlistType::resolve(null, ['id' => $item->id], $info);
}
} else {
//no access given
error_log('no access');
}
return empty($resultArray)? null :$resultArray;
} else {
return null;
}
}
}
使用这两个文件,您将获得对 ProspectLists 的 QUERY 支持(此示例中 prospectlist 本身缺失)
Prospectlists 有助于对受众、联系人、账户和潜在客户进行细分,因此以下突变将关联 Bean 和潜在客户。
<?php
//schema/ProspectlistprospectInputType.php
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Type\Scalar\StringType;
class ProspectlistprospectInputType extends AbstractObjectType // extending abstract Object type
{
public function build($config) // implementing an abstract function where you build your type
{
$config->addField('id', new StringType());
}
public static function guidv4() //Helper function to imitate mysql GUID
{
if (function_exists('com_create_guid') === true)
return trim(com_create_guid(), '{}');
$data = openssl_random_pseudo_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
public function resolve($value = null, $args = [], $type = null) // implementing resolve function
{
//We use the crm Helper to create/save the Bean
global $db;
if(!empty($args['beanid']) && !empty($args['prospectlistid']) && !empty($args['beanname'])
&& ($args['beanname'] =='Accounts' || $args['beanname'] =="Contacts"||$args['beanname']== "Prospects" )
){
$insertQuery="
INSERT INTO prospect_lists_prospects
(id, prospect_list_id, related_id, related_type, date_modified)
VALUES('?', '?', '?', '?', now());
";
$uuid=\ProspectlistprospectInputType::guidv4();
$resultByStatus = $db->pQuery($insertQuery, [$uuid, $args['prospectlistid'],$args['beanid'],$args['beanname']]);
if($resultByStatus=="1"){
return $uuid;
}else{
return null;
}
}else{
return null;
}
}
public function getName()
{
return 'Prospectlistprospect';
}
public function getOutputType()
{
return new StringType();
}
}
自定义 Rest 端点
- 当您的根 suitecrm/lionixcrm 文件夹中存在文件
graphql/CustomRest.php时,您可以扩展由 Slim 创建的 rest 请求。
$app->post('/newPost', function (\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response){
return $response->withJson(["result"=>"empty - not even tried"]);
}
CORS
如果您需要 CORS 支持,您可以使用 graphql/CustomRest.php 并包含以下片段。
$app->add(new \Eko3alpha\Slim\Middleware\CorsMiddleware([
'http://www.example.com' => ['GET', 'POST','PUT', 'DELETE','OPTIONS'],
'https://www.example2.com' => ['GET', 'POST'],
]));
CORS 中间件将负责允许指定的主机访问 API 并返回有效的标头。
GraphiQL
您可以使用以下 GraphiQL:vendor/lionixevolve/graphqlsuitecrm/graphql/GraphiQL/
已知问题
- 当前存在一个与模式检查器的问题 Issue#2
- 目前正在保存 Bean 时检查 ACL 编辑权限。
TODO
- 添加缺失的模块(报价/发票/潜在客户列表等)
- 迁移到更活跃的 webonyx graphqlphp