APIv4 Explorer - Add REST syntax
authorColeman Watts <coleman@civicrm.org>
Mon, 7 Feb 2022 17:23:49 +0000 (12:23 -0500)
committerColeman Watts <coleman@civicrm.org>
Tue, 8 Feb 2022 13:54:34 +0000 (08:54 -0500)
CRM/Api4/Page/Api4Explorer.php
ang/api4Explorer/Explorer.html
ang/api4Explorer/Explorer.js

index a31e5feb62ece31f756e617173ecdefa92b49a33..249991cfeed1341cf4090ca92ddec500e51084d0 100644 (file)
@@ -22,6 +22,7 @@ class CRM_Api4_Page_Api4Explorer extends CRM_Core_Page {
   public function run() {
     $apiDoc = new ReflectionFunction('civicrm_api4');
     $groupOptions = civicrm_api4('Group', 'getFields', ['loadOptions' => TRUE, 'select' => ['options', 'name'], 'where' => [['name', 'IN', ['visibility', 'group_type']]]]);
+    $extensions = \CRM_Extension_System::singleton()->getMapper();
 
     $vars = [
       'operators' => CoreUtil::getOperators(),
@@ -30,6 +31,8 @@ class CRM_Api4_Page_Api4Explorer extends CRM_Core_Page {
       'docs' => \Civi\Api4\Utils\ReflectionUtils::parseDocBlock($apiDoc->getDocComment()),
       'functions' => self::getSqlFunctions(),
       'groupOptions' => array_column((array) $groupOptions, 'options', 'name'),
+      'authxEnabled' => $extensions->isActiveModule('authx'),
+      'restUrl' => rtrim(CRM_Utils_System::url('civicrm/ajax/api4/CRMAPI4ENTITY/CRMAPI4ACTION', NULL, TRUE, NULL, FALSE), '/'),
     ];
     Civi::resources()
       ->addVars('api4', $vars)
index a170e2aa847d36f5ef5e3dab6d095692ff61d6e7..a48a68f50aaafd5103764ac3af5deb5a57607c66 100644 (file)
         </ul>
       </div>
       <div class="panel-body">
+        <div class="alert alert-danger" ng-if="selectedTab.code === 'rest' && !$ctrl.authxEnabled">
+          <p>
+            {{:: ts('To enable REST authentication, the AuthX extension must be installed.') }}
+            <a target="_blank" ng-href="{{ crmUrl('civicrm/admin/extensions') }}">
+              <i class="crm-i fa-gear"> {{:: ts('Manage Extensions') }}</i>
+            </a>
+          </p>
+        </div>
+        <div class="alert alert-warning" ng-if="selectedTab.code === 'rest' && $ctrl.authxEnabled">
+          <p>
+            <a target="_blank" ng-href="{{ crmUrl('civicrm/admin/setting/authx', {reset: 1}) }}">
+              <i class="crm-i fa-gear"> {{:: ts('Configure REST Authentication') }}</i>
+            </a>
+          </p>
+          <p>
+            <a target="_blank" href="https://docs.civicrm.org/dev/en/latest/api/v4/rest/">
+              <i class="crm-i fa-external-link"> {{:: ts('REST Documentation') }}</i>
+            </a>
+          </p>
+        </div>
         <div ng-repeat="style in code[selectedTab.code]">
           <label>{{:: style.label }}</label>
           <div><pre class="prettyprint" ng-bind-html="style.code"></pre></div>
index dcfc702e2695fa680f321635d3cc17a7e7a5a41c..151aa1c8d400bf4d602fd255e4974495b1ce38f9 100644 (file)
@@ -34,6 +34,7 @@
     params = $scope.params = {};
     $scope.index = '';
     $scope.selectedTab = {result: 'result'};
+    $scope.crmUrl = CRM.url;
     $scope.perm = {
       accessDebugOutput: CRM.checkPerm('access debug output'),
       editGroups: CRM.checkPerm('edit groups')
@@ -53,7 +54,7 @@
     $scope.status = 'default';
     $scope.loading = false;
     $scope.controls = {};
-    $scope.langs = ['php', 'js', 'ang', 'cli'];
+    $scope.langs = ['php', 'js', 'ang', 'cli', 'rest'];
     $scope.joinTypes = [{k: 'LEFT', v: 'LEFT JOIN'}, {k: 'INNER', v: 'INNER JOIN'}, {k: 'EXCLUDE', v: 'EXCLUDE'}];
     $scope.bridgeEntities = _.filter(schema, function(entity) {return _.includes(entity.type, 'EntityBridge');});
     $scope.code = {
         {name: 'short', label: ts('CV (short)'), code: ''},
         {name: 'long', label: ts('CV (long)'), code: ''},
         {name: 'pipe', label: ts('CV (pipe)'), code: ''}
+      ],
+      rest: [
+        {name: 'curl', label: ts('Curl'), code: ''},
+        {name: 'restphp', label: ts('PHP (std)'), code: ''},
+        {name: 'guzzle', label: ts('PHP + Guzzle'), code: ''}
       ]
     };
     this.resultFormats = [
@@ -85,6 +91,7 @@
         label: ts('View as PHP')
       },
     ];
+    this.authxEnabled = CRM.vars.api4.authxEnabled;
 
     if (!entities.length) {
       formatForSelect2(schema, entities, 'name', ['description', 'icon']);
       return str.trim();
     }
 
+    // Url-encode suitable for use in a bash script
+    function curlEscape(str) {
+      return encodeURIComponent(str).
+        replace(/['()*]/g, function(c) {
+          return "%" + c.charCodeAt(0).toString(16);
+        });
+    }
+
     function writeCode() {
       var code = {},
         entity = $scope.entity,
                   code.short += ' ' + key + '=' + (typeof param === 'string' ? cliFormat(param) : cliFormat(JSON.stringify(param)));
               }
             });
+            break;
+
+          case 'rest':
+            var restUrl = CRM.vars.api4.restUrl
+              .replace('CRMAPI4ENTITY', entity)
+              .replace('CRMAPI4ACTION', action);
+            var cleanUrl;
+            if (CRM.vars.api4.restUrl.endsWith('/CRMAPI4ENTITY/CRMAPI4ACTION')) {
+              cleanUrl = CRM.vars.api4.restUrl.replace('/CRMAPI4ENTITY/CRMAPI4ACTION', '/');
+            }
+            var restCred = 'Bearer MY_API_KEY';
+
+            // CURL
+            code.curl =
+              "CRM_URL='" + restUrl + "'\n" +
+              "CRM_AUTH='X-Civi-Auth: " + restCred + "'\n\n" +
+              'curl -X POST -H "$CRM_AUTH" "$CRM_URL" \\' + "\n" +
+              "-d 'params=" + curlEscape(JSON.stringify(params));
+            if (index || index === 0) {
+              code.curl += '&index=' + curlEscape(JSON.stringify(index));
+            }
+            code.curl += "'";
+
+            var queryParams = "['params' => json_encode($params)" +
+              ((typeof index === 'number') ? ", 'index' => " + JSON.stringify(index) : '') +
+              ((index && typeof index !== 'number') ? ", 'index' => json_encode(" + phpFormat(index) + ')' : '') +
+              "]";
+
+            // Guzzle
+            code.guzzle =
+              "$params = " + phpFormat(params, 2) + ";\n" +
+              "$client = new \\GuzzleHttp\\Client([\n" +
+              (cleanUrl ? "  'base_uri' => '" + cleanUrl + "',\n" : '') +
+              "  'headers' => ['X-Civi-Auth' => " + phpFormat(restCred) + "],\n" +
+              "]);\n" +
+              "$response = $client->get('" + (cleanUrl ? entity + '/' + action : restUrl) + "', [\n" +
+              "  'form_params' => " + queryParams + ",\n" +
+              "]);\n" +
+              '$' + results + " = json_decode((string) $response->getBody(), TRUE);";
+
+            // PHP StdLib
+            code.restphp =
+              "$url = '" + restUrl + "';\n" +
+              "$params = " + phpFormat(params, 2) + ";\n" +
+              "$request = stream_context_create([\n" +
+              "  'http' => [\n" +
+              "    'method' => 'POST',\n" +
+              "    'header' => [\n" +
+              "      'Content-Type: application/x-www-form-urlencoded',\n" +
+              "      " + phpFormat('X-Civi-Auth: ' + restCred) + ",\n" +
+              "    ],\n" +
+              "    'content' => http_build_query(" + queryParams + "),\n" +
+              "  ]\n" +
+              "]);\n" +
+              '$' + results + " = json_decode(file_get_contents($url, FALSE, $request), TRUE);\n";
         }
       }
       _.each($scope.code, function(vals) {