lionixevolve/graphqlsuitecrm

SuiteCRM / SugarCRM CE 的 GraphQL 支持

安装: 223

依赖: 0

建议: 0

安全: 0

星标: 19

关注者: 8

分支: 2

开放问题: 5

语言:JavaScript

v0.13.4 2021-07-09 00:44 UTC

README

Latest Stable Version Total Downloads Latest Unstable Version License

即将推出的新版本 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 文件夹加载自定义文件。

加载了两个主要文件

  1. graphql/CustomSuiteCRMSchema.php
  2. graphql/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