$scope.actions = actions;
$scope.fields = [];
$scope.fieldsAndJoins = [];
+ $scope.selectFieldsAndJoins = [];
$scope.availableParams = {};
$scope.params = {};
$scope.index = '';
+ $scope.selectedTab = {result: 'result', code: 'php'};
+ $scope.perm = {
+ accessDebugOutput: CRM.checkPerm('access debug output')
+ };
var getMetaParams = {},
objectParams = {orderBy: 'ASC', values: '', chain: ['Entity', '', '{}']},
+ docs = CRM.vars.api4.docs,
helpTitle = '',
helpContent = {};
$scope.helpTitle = '';
$scope.helpContent = {};
$scope.entity = $routeParams.api4entity;
$scope.result = [];
+ $scope.debug = null;
$scope.status = 'default';
$scope.loading = false;
$scope.controls = {};
- $scope.codeLabel = {
- oop: ts('PHP (oop style)'),
- php: ts('PHP (traditional)'),
- js: ts('Javascript'),
- cli: ts('Command Line')
- };
- $scope.code = codeDefaults();
-
- function codeDefaults() {
- return _.mapValues($scope.codeLabel, function(val, key) {
- return key === 'oop' ? ts('Select an entity and action') : '';
- });
- }
+ $scope.code = [
+ {
+ lang: 'php',
+ style: [
+ {name: 'oop', label: ts('OOP Style'), code: ''},
+ {name: 'php', label: ts('Traditional'), code: ''}
+ ]
+ },
+ {
+ lang: 'js',
+ style: [
+ {name: 'js', label: ts('Single Call'), code: ''},
+ {name: 'js2', label: ts('Batch Calls'), code: ''}
+ ]
+ },
+ {
+ lang: 'ang',
+ style: [
+ {name: 'ang', label: ts('Single Call'), code: ''},
+ {name: 'ang2', label: ts('Batch Calls'), code: ''}
+ ]
+ },
+ {
+ lang: 'cli',
+ style: [
+ {name: 'cv', label: ts('CV'), code: ''}
+ ]
+ },
+ ];
if (!entities.length) {
formatForSelect2(schema, entities, 'name', ['description']);
return container;
}
- function getFieldList(source) {
+ function getFieldList(action) {
var fields = [],
- fieldInfo = _.findWhere(getEntity().actions, {name: $scope.action}).fields;
+ fieldInfo = _.findWhere(getEntity().actions, {name: action}).fields;
formatForSelect2(fieldInfo, fields, 'name', ['description', 'required', 'default_value']);
return fields;
}
- function addJoins(fieldList) {
+ function addJoins(fieldList, addWildcard) {
var fields = _.cloneDeep(fieldList),
fks = _.findWhere(links, {entity: $scope.entity}) || {};
_.each(fks.links, function(link) {
- var linkFields = entityFields(link.entity);
+ var linkFields = _.cloneDeep(entityFields(link.entity)),
+ wildCard = addWildcard ? [{id: link.alias + '.*', text: link.alias + '.*', 'description': 'All core ' + link.entity + ' fields'}] : [];
if (linkFields) {
fields.push({
text: link.alias,
description: 'Join to ' + link.entity,
- children: formatForSelect2(linkFields, [], 'name', ['description'], link.alias + '.')
+ children: wildCard.concat(formatForSelect2(linkFields, [], 'name', ['description'], link.alias + '.'))
});
}
});
}
};
+ // Format the href for a @see help annotation
+ $scope.formatRef = function(see) {
+ var match = see.match(/^\\Civi\\Api4\\([a-zA-Z]+)$/);
+ if (match) {
+ return '#/explorer/' + match[1];
+ }
+ if (see[0] === '\\') {
+ return 'https://github.com/civicrm/civicrm-core/blob/master' + see.replace(/\\/i, '/') + '.php';
+ }
+ return see;
+ };
+
$scope.fieldHelp = function(fieldName) {
var field = getField(fieldName, $scope.entity, $scope.action);
if (!field) {
};
$scope.valuesFields = function() {
- var fields = _.cloneDeep($scope.fields);
+ var fields = _.cloneDeep($scope.action === 'getFields' ? getFieldList($scope.params.action || 'get') : $scope.fields);
// Disable fields that are already in use
_.each($scope.params.values || [], function(val) {
(_.findWhere(fields, {id: val[0]}) || {}).disabled = true;
$scope.params.select = [];
} else {
$scope.params.select = ['row_count'];
+ $scope.index = '';
if ($scope.params.limit == 25) {
$scope.params.limit = 0;
}
function selectAction() {
$scope.action = $routeParams.api4action;
- $scope.fieldsAndJoins = [];
+ $scope.fieldsAndJoins.length = 0;
+ $scope.selectFieldsAndJoins.length = 0;
if (!actions.length) {
formatForSelect2(getEntity().actions, actions, 'name', ['description', 'params']);
}
if ($scope.action) {
var actionInfo = _.findWhere(actions, {id: $scope.action});
- $scope.fields = getFieldList();
+ $scope.fields = getFieldList($scope.action);
if (_.contains(['get', 'update', 'delete', 'replace'], $scope.action)) {
$scope.fieldsAndJoins = addJoins($scope.fields);
+ $scope.selectFieldsAndJoins = addJoins($scope.fields, true);
} else {
$scope.fieldsAndJoins = $scope.fields;
+ $scope.selectFieldsAndJoins = _.cloneDeep($scope.fields);
}
+ $scope.selectFieldsAndJoins.unshift({id: '*', text: '*', 'description': 'All core ' + $scope.entity + ' fields'});
_.each(actionInfo.params, function (param, name) {
var format,
defaultVal = _.cloneDeep(param.default);
default:
format = 'raw';
}
- if (name == 'limit') {
+ if (name === 'limit') {
defaultVal = 25;
}
+ if (name === 'debug') {
+ defaultVal = true;
+ }
if (name === 'values') {
defaultVal = defaultValues(defaultVal);
}
}
function writeCode() {
- var code = codeDefaults(),
+ var code = {},
entity = $scope.entity,
action = $scope.action,
params = getParams(),
- index = isInt($scope.index) ? +$scope.index : $scope.index,
+ index = isInt($scope.index) ? +$scope.index : parseYaml($scope.index),
result = 'result';
if ($scope.entity && $scope.action) {
+ delete params.debug;
if (action.slice(0, 3) === 'get') {
result = entity.substr(0, 7) === 'Custom_' ? _.camelCase(entity.substr(7)) : entity;
result = lcfirst(action.replace(/s$/, '').slice(3) || result);
}
// Write javascript
- code.js = "CRM.api4('" + entity + "', '" + action + "', {";
+ var js = "'" + entity + "', '" + action + "', {";
_.each(params, function(param, key) {
- code.js += "\n " + key + ': ' + stringify(param) +
+ js += "\n " + key + ': ' + stringify(param) +
(++i < paramCount ? ',' : '');
if (key === 'checkPermissions') {
- code.js += ' // IGNORED: permissions are always enforced from client-side requests';
+ js += ' // IGNORED: permissions are always enforced from client-side requests';
}
});
- code.js += "\n}";
+ js += "\n}";
if (index || index === 0) {
- code.js += ', ' + JSON.stringify(index);
+ js += ', ' + JSON.stringify(index);
}
- code.js += ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});";
+ code.js = "CRM.api4(" + js + ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});";
+ code.js2 = "CRM.api4({" + results + ': [' + js + "]}).then(function(batch) {\n // do something with batch." + results + " array\n}, function(failure) {\n // handle failure\n});";
+ code.ang = "crmApi4(" + js + ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});";
+ code.ang2 = "crmApi4({" + results + ': [' + js + "]}).then(function(batch) {\n // do something with batch." + results + " array\n}, function(failure) {\n // handle failure\n});";
// Write php code
code.php = '$' + results + " = civicrm_api4('" + entity + "', '" + action + "', [";
code.php += ', ' + phpFormat(index);
}
code.php += ");";
-
+
// Write oop code
if (entity.substr(0, 7) !== 'Custom_') {
code.oop = '$' + results + " = \\Civi\\Api4\\" + entity + '::' + action + '()';
}
});
code.oop += "\n ->execute()";
- if (_.isNumber(index)) {
+ if (isSelectRowCount) {
+ code.oop += "\n ->count()";
+ } else if (_.isNumber(index)) {
code.oop += !index ? '\n ->first()' : (index === -1 ? '\n ->last()' : '\n ->itemAt(' + index + ')');
} else if (index) {
- code.oop += "\n ->indexBy('" + index + "')";
- } else if (isSelectRowCount) {
- code.oop += "\n ->count()";
+ if (_.isString(index) || (_.isPlainObject(index) && !index[0] && !index['0'])) {
+ code.oop += "\n ->indexBy('" + (_.isPlainObject(index) ? _.keys(index)[0] : index) + "')";
+ }
+ if (_.isArray(index) || _.isPlainObject(index)) {
+ code.oop += "\n ->column('" + (_.isArray(index) ? index[0] : _.values(index)[0]) + "')";
+ }
}
code.oop += ";\n";
if (!_.isNumber(index) && !isSelectRowCount) {
}
// Write cli code
- code.cli = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'";
+ code.cv = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'";
}
- _.each(code, function(val, type) {
- $scope.code[type] = prettyPrintOne(val);
+ _.each($scope.code, function(vals) {
+ _.each(vals.style, function(style) {
+ style.code = code[style.name] ? prettyPrintOne(code[style.name]) : '';
+ });
});
}
ret += (ret.length ? ', ' : '') + key + ': ' + (_.isArray(val) ? '[' + val + ']' : val);
}
});
- return prettyPrintOne(ret);
+ return prettyPrintOne(_.escape(ret));
}
$scope.execute = function() {
$scope.loading = true;
$http.post(CRM.url('civicrm/ajax/api4/' + $scope.entity + '/' + $scope.action, {
params: angular.toJson(getParams()),
- index: $scope.index
+ index: isInt($scope.index) ? +$scope.index : parseYaml($scope.index)
}), null, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}).then(function(resp) {
$scope.loading = false;
$scope.status = 'success';
- $scope.result = [formatMeta(resp.data), prettyPrintOne(JSON.stringify(resp.data.values, null, 2), 'js', 1)];
+ $scope.debug = debugFormat(resp.data);
+ $scope.result = [formatMeta(resp.data), prettyPrintOne(_.escape(JSON.stringify(resp.data.values, null, 2)), 'js', 1)];
}, function(resp) {
$scope.loading = false;
$scope.status = 'danger';
- $scope.result = [formatMeta(resp), prettyPrintOne(JSON.stringify(resp.data, null, 2))];
+ $scope.debug = debugFormat(resp.data);
+ $scope.result = [formatMeta(resp), prettyPrintOne(_.escape(JSON.stringify(resp.data, null, 2)))];
});
};
+ function debugFormat(data) {
+ var debug = data.debug ? prettyPrintOne(_.escape(JSON.stringify(data.debug, null, 2)).replace(/\\n/g, "\n")) : null;
+ delete data.debug;
+ return debug;
+ }
+
/**
* Format value to look like php code
*/
$scope.helpTitle = helpTitle = $scope.entity;
$scope.helpContent = helpContent = {
description: entityInfo.description,
- comment: entityInfo.comment
+ comment: entityInfo.comment,
+ see: entityInfo.see
};
}
if (!$scope.entity) {
- $scope.helpTitle = helpTitle = ts('Help');
- $scope.helpContent = helpContent = {description: ts('Welcome to the api explorer.'), comment: ts('Select an entity to begin.')};
+ $scope.helpTitle = helpTitle = ts('APIv4 Explorer');
+ $scope.helpContent = helpContent = {description: docs.description, comment: docs.comment, see: docs.see};
} else if (!actions.length && !getEntity().actions) {
getMetaParams.actions = [$scope.entity, 'getActions', {chain: {fields: [$scope.entity, 'getFields', {action: '$name'}]}}];
fetchMeta();
$location.url('/explorer/' + $scope.entity + '/' + newVal);
} else if (newVal) {
$scope.helpTitle = helpTitle = $scope.entity + '::' + newVal;
- $scope.helpContent = helpContent = _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment']);
+ $scope.helpContent = helpContent = _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment', 'see']);
}
});
- $scope.indexHelp = {
- description: ts('(string|int) Index results or select by index.'),
- comment: ts('Pass a string to index the results by a field value. E.g. index: "name" will return an associative array with names as keys.') + '\n\n' +
- ts('Pass an integer to return a single result; e.g. index: 0 will return the first result, 1 will return the second, and -1 will return the last.')
+ $scope.paramDoc = function(name) {
+ return docs.params[name];
};
$scope.$watch('params', writeCode, true);
var ts = scope.ts = CRM.ts(),
multi = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], scope.data.op),
entity = $routeParams.api4entity,
- action = $routeParams.api4action;
+ action = scope.data.action || $routeParams.api4action;
function destroyWidget() {
var $el = $(element);
});
} else if (dataType === 'Boolean') {
$el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [
- {id: '1', text: ts('Yes')},
- {id: '0', text: ts('No')}
+ {id: 'true', text: ts('Yes')},
+ {id: 'false', text: ts('No')}
]});
}
} else if (dataType === 'Integer' && !multi) {