SearchKit - permit selection of current user id for dynamic searches
[civicrm-core.git] / ang / api4Explorer / Explorer.js
index 0dca04e3bc463cacd76b7bbb8280a0dd5ddb7a74..fb9f5c71ce0d03ccc3c7218e65de3734b5492681 100644 (file)
@@ -57,7 +57,7 @@
     $scope.controls = {};
     $scope.langs = ['php', 'js', 'ang', 'cli'];
     $scope.joinTypes = [{k: false, v: 'FALSE (LEFT JOIN)'}, {k: true, v: 'TRUE (INNER JOIN)'}];
-    $scope.bridgeEntities = _.filter(schema, {type: 'BridgeEntity'});
+    $scope.bridgeEntities = _.filter(schema, function(entity) {return _.includes(entity.type, 'EntityBridge');});
     $scope.code = {
       php: [
         {name: 'oop', label: ts('OOP Style'), code: ''},
@@ -72,7 +72,9 @@
         {name: 'ang2', label: ts('Batch Calls'), code: ''}
       ],
       cli: [
-        {name: 'cv', label: ts('CV'), code: ''}
+        {name: 'short', label: ts('CV (short)'), code: ''},
+        {name: 'long', label: ts('CV (long)'), code: ''},
+        {name: 'pipe', label: ts('CV (pipe)'), code: ''}
       ]
     };
 
       if (lastLetter === 's' || lastLetter === 'x' || lastTwo === 'ch') {
         return str + 'es';
       }
-      if (lastLetter === 'y' && lastTwo !== 'ey') {
+      if (lastLetter === 'y' && !_.includes(['ay', 'ey', 'iy', 'oy', 'uy'], lastTwo)) {
         return str.slice(0, -1) + 'ies';
       }
       return str + 's';
       }
       // Then lookup implicit links
       _.each(path, function(node) {
-        entity = _.find(links[entity], {alias: node}).entity;
+        var link = _.find(links[entity], {alias: node});
+        if (!link) {
+          return false;
+        }
+        entity = link.entity;
       });
       return entity;
     }
             break;
 
           case 'cli':
-            // Write cli code
-            code.cv = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'";
+            // Cli code using json input
+            code.long = 'cv api4 ' + entity + '.' + action + ' ' + cliFormat(JSON.stringify(params));
+            code.pipe = 'echo ' + cliFormat(JSON.stringify(params)) + ' | cv api4 ' + entity + '.' + action + ' --in=json';
+
+            // Cli code using short syntax
+            code.short = 'cv api4 ' + entity + '.' + action;
+            var limitSet = false;
+            _.each(params, function(param, key) {
+              switch (true) {
+                case (key === 'select' && !_.includes(param.join(), ' ')):
+                  code.short += ' +s ' + cliFormat(param.join(','));
+                  break;
+                case (key === 'where' && !_.intersection(_.map(param, 0), ['AND', 'OR', 'NOT']).length):
+                  _.each(param, function(clause) {
+                    code.short += ' +w ' + cliFormat(clause[0] + ' ' + clause[1] + (clause.length > 2 ? (' ' + JSON.stringify(clause[2])) : ''));
+                  });
+                  break;
+                case (key === 'orderBy'):
+                  _.each(param, function(dir, field) {
+                    code.short += ' +o ' + cliFormat(field + ' ' + dir);
+                  });
+                  break;
+                case (key === 'values'):
+                  _.each(param, function(val, field) {
+                    code.short += ' +v ' + cliFormat(field + '=' + val);
+                  });
+                  break;
+                case (key === 'limit' || key === 'offset'):
+                  // These 2 get combined
+                  if (!limitSet) {
+                    limitSet = true;
+                    code.short += ' +l ' + (params.limit || '0') + (params.offset ? ('@' + params.offset) : '');
+                  }
+                  break;
+                default:
+                  code.short += ' ' + key + '=' + (typeof param === 'string' ? cliFormat(param) : cliFormat(JSON.stringify(param)));
+              }
+            });
         }
       }
       _.each($scope.code, function(vals) {
           $scope.debug = debugFormat(resp.data);
           $scope.result = [
             formatMeta(resp.data),
-            prettyPrintOne('(' + resp.data.values.length + ') ' + _.escape(JSON.stringify(resp.data.values, null, 2)), 'js', 1)
+            prettyPrintOne((_.isArray(resp.data.values) ? '(' + resp.data.values.length + ') ' : '') + _.escape(JSON.stringify(resp.data.values, null, 2)), 'js', 1)
           ];
         }, function(resp) {
           $scope.loading = false;
       return JSON.stringify(val).replace(/\$/g, '\\$');
     }
 
+    // Format string to be cli-input-safe
+    function cliFormat(str) {
+      if (!_.includes(str, ' ') && !_.includes(str, '"') && !_.includes(str, "'")) {
+        return str;
+      }
+      if (!_.includes(str, "'")) {
+        return "'" + str + "'";
+      }
+      if (!_.includes(str, '"')) {
+        return '"' + str + '"';
+      }
+      return "'" + str.replace(/'/g, "\\'") + "'";
+    }
+
     function fetchMeta() {
       crmApi4(getMetaParams)
         .then(function(data) {
       setHelp($scope.entity, {
         description: entityInfo.description,
         comment: entityInfo.comment,
+        type: entityInfo.type,
         see: entityInfo.see
       });
     }
     };
   });
 
-  angular.module('api4Explorer').directive('crmApi4Clause', function() {
-    return {
-      scope: {
-        data: '<crmApi4Clause'
-      },
-      templateUrl: '~/api4Explorer/Clause.html',
-      controller: function ($scope, $element, $timeout) {
-        var ts = $scope.ts = CRM.ts(),
-          ctrl = $scope.$ctrl = this;
-        this.conjunctions = {AND: ts('And'), OR: ts('Or'), NOT: ts('Not')};
-        this.operators = CRM.vars.api4.operators;
-        this.sortOptions = {
-          axis: 'y',
-          connectWith: '.api4-clause-group-sortable',
-          containment: $element.closest('.api4-clause-fieldset'),
-          over: onSortOver,
-          start: onSort,
-          stop: onSort
-        };
+  angular.module('api4Explorer').component('crmApi4Clause', {
+    bindings: {
+      fields: '<',
+      clauses: '<',
+      format: '@',
+      op: '@',
+      skip: '<',
+      isRequired: '<',
+      label: '@',
+      deleteGroup: '&'
+    },
+    templateUrl: '~/api4Explorer/Clause.html',
+    controller: function ($scope, $element, $timeout) {
+      var ts = $scope.ts = CRM.ts(),
+        ctrl = this;
+      this.conjunctions = {AND: ts('And'), OR: ts('Or'), NOT: ts('Not')};
+      this.operators = CRM.vars.api4.operators;
+      this.sortOptions = {
+        axis: 'y',
+        connectWith: '.api4-clause-group-sortable',
+        containment: $element.closest('.api4-clause-fieldset'),
+        over: onSortOver,
+        start: onSort,
+        stop: onSort
+      };
 
-        this.addGroup = function(op) {
-          $scope.data.clauses.push([op, []]);
-        };
+      this.$onInit = function() {
+        ctrl.hasParent = !!$element.attr('delete-group');
+      };
 
-        this.removeGroup = function() {
-          $scope.data.groupParent.splice($scope.data.groupIndex, 1);
-        };
+      this.addGroup = function(op) {
+        ctrl.clauses.push([op, []]);
+      };
+
+      function onSort(event, ui) {
+        $($element).closest('.api4-clause-fieldset').toggleClass('api4-sorting', event.type === 'sortstart');
+        $('.api4-input.form-inline').css('margin-left', '');
+      }
 
-        function onSort(event, ui) {
-          $($element).closest('.api4-clause-fieldset').toggleClass('api4-sorting', event.type === 'sortstart');
-          $('.api4-input.form-inline').css('margin-left', '');
+      // Indent clause while dragging between nested groups
+      function onSortOver(event, ui) {
+        var offset = 0;
+        if (ui.sender) {
+          offset = $(ui.placeholder).offset().left - $(ui.sender).offset().left;
         }
+        $('.api4-input.form-inline.ui-sortable-helper').css('margin-left', '' + offset + 'px');
+      }
 
-        // Indent clause while dragging between nested groups
-        function onSortOver(event, ui) {
-          var offset = 0;
-          if (ui.sender) {
-            offset = $(ui.placeholder).offset().left - $(ui.sender).offset().left;
+      this.addClause = function() {
+        $timeout(function() {
+          if (ctrl.newClause) {
+            if (ctrl.skip && ctrl.clauses.length < ctrl.skip) {
+              ctrl.clauses.push(null);
+            }
+            ctrl.clauses.push([ctrl.newClause, '=', '']);
+            ctrl.newClause = null;
           }
-          $('.api4-input.form-inline.ui-sortable-helper').css('margin-left', '' + offset + 'px');
+        });
+      };
+
+      this.deleteRow = function(index) {
+        ctrl.clauses.splice(index, 1);
+      };
+
+      // Remove empty values
+      this.changeClauseField = function(clause, index) {
+        if (clause[0] === '') {
+          ctrl.deleteRow(index);
         }
+      };
 
-        this.addClause = function() {
-          $timeout(function() {
-            if (ctrl.newClause) {
-              $scope.data.clauses.push([ctrl.newClause, '=', '']);
-              ctrl.newClause = null;
-            }
-          });
-        };
-        $scope.$watch('data.clauses', function(values) {
-          // Iterate in reverse order so index doesn't get out-of-sync during splice
-          _.forEachRight(values, function(clause, index) {
-            // Remove empty values
-            if (index >= ($scope.data.skip  || 0)) {
-              if (typeof clause !== 'undefined' && !clause[0]) {
-                values.splice(index, 1);
-              }
-              // Add/remove value if operator allows for one
-              else if (typeof clause[1] === 'string' && _.contains(clause[1], 'NULL')) {
-                clause.length = 2;
-              } else if (typeof clause[1] === 'string' && clause.length === 2) {
-                clause.push('');
-              }
-            }
-          });
-        }, true);
-      }
-    };
+      // Add/remove value if operator allows for one
+      this.changeClauseOperator = function(clause) {
+        if (_.contains(clause[1], 'NULL')) {
+          clause.length = 2;
+        } else if (clause.length === 2) {
+          clause.push('');
+        }
+      };
+    }
   });
 
   angular.module('api4Explorer').directive('api4ExpValue', function($routeParams, crmApi4) {
                 $el.removeClass('loading').crmSelect2({data: options, multiple: multi});
               });
             } else if (field.fk_entity) {
-              $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}});
+              $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}, static: field.fk_entity === 'Contact' ? ['user_contact_id'] : []});
             } else if (dataType === 'Boolean') {
               $el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [
                 {id: 'true', text: ts('Yes')},