Merge pull request #4720 from rohankatkar/Tests
[civicrm-core.git] / templates / CRM / Admin / Page / APIExplorer.js
CommitLineData
e4176358
CW
1(function($, _, undefined) {
2 var
3 entity,
4 action,
a14e9d08 5 actions = {values: ['get']},
e4176358 6 fields = [],
fc6a6a51 7 getFieldData = {},
e4176358
CW
8 options = {},
9 params = {},
2b6e1174 10 smartyStub,
8ffa1387 11 fieldTpl = _.template($('#api-param-tpl').html()),
c275764e 12 optionsTpl = _.template($('#api-options-tpl').html()),
b07af612 13 returnTpl = _.template($('#api-return-tpl').html()),
77099ee0
CW
14 chainTpl = _.template($('#api-chain-tpl').html()),
15
16 // Operators with special properties
17 BOOL = ['IS NULL', 'IS NOT NULL'],
18 TEXT = ['LIKE', 'NOT LIKE'],
19 MULTI = ['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'];
0c3a6e64 20
2b6e1174
CW
21 /**
22 * Call prettyPrint function if it successfully loaded from the cdn
23 */
cd77ffa6
CW
24 function prettyPrint() {
25 if (window.prettyPrint) {
26 window.prettyPrint();
27 }
28 }
29
b07af612
CW
30 /**
31 * Add a "fields" row
32 * @param name
33 */
e4176358
CW
34 function addField(name) {
35 $('#api-params').append($(fieldTpl({name: name || ''})));
36 var $row = $('tr:last-child', '#api-params');
77099ee0 37 $('input.api-param-name', $row).crmSelect2({
b07af612
CW
38 data: fields.concat({id: '-', text: ts('Other') + '...'})
39 }).change();
8ffa1387
CW
40 }
41
c275764e
CW
42 /**
43 * Add a new "options" row
44 * @param name
45 */
46 function addOptionField(name) {
47 if ($('.api-options-row', '#api-params').length) {
48 $('.api-options-row:last', '#api-params').after($(optionsTpl({})));
49 } else {
50 $('#api-params').append($(optionsTpl({})));
51 }
52 var $row = $('.api-options-row:last', '#api-params');
53 $('.api-option-name', $row).crmSelect2({data: [
54 {id: 'limit', text: 'limit'},
55 {id: 'offset', text: 'offset'},
56 {id: 'sort', text: 'sort'},
57 {id: 'metadata', text: 'metadata'},
58 {id: '-', text: ts('Other') + '...'}
59 ]});
60 }
61
b07af612
CW
62 /**
63 * Add an "api chain" row
64 */
8ffa1387
CW
65 function addChainField() {
66 $('#api-params').append($(chainTpl({})));
67 var $row = $('tr:last-child', '#api-params');
68 $('.api-chain-entity', $row).crmSelect2({
69 formatSelection: function(item) {
70 return '<span class="icon ui-icon-link"></span> API ' + item.text;
2b6e1174
CW
71 },
72 placeholder: '<span class="icon ui-icon-link"></span> ' + ts('Entity'),
73 escapeMarkup: function(m) {return m}
8ffa1387 74 });
0c3a6e64
CW
75 }
76
b07af612
CW
77 /**
78 * Fetch fields for entity+action
79 */
a14e9d08 80 function getFields(changedElement) {
e4176358
CW
81 var required = [];
82 fields = [];
fc6a6a51 83 options = getFieldData = {};
e4176358
CW
84 // Special case for getfields
85 if (action === 'getfields') {
86 fields.push({
87 id: 'api_action',
88 text: 'Action'
89 });
77d80c9f
CW
90 options.api_action = _.reduce(actions.values, function(ret, item) {
91 ret[item] = item;
92 return ret;
93 }, {});
e4176358 94 showFields(['api_action']);
0c3a6e64
CW
95 return;
96 }
fc6a6a51
CW
97 CRM.api3(entity, 'getFields', {'api_action': action, options: {get_options: 'all'}}).done(function(data) {
98 getFieldData = data.values;
e4176358
CW
99 _.each(data.values, function(field) {
100 if (field.name) {
101 fields.push({
102 id: field.name,
103 text: field.title || field.name,
104 required: field['api.required'] || false
105 });
106 if (field['api.required']) {
107 required.push(field.name);
108 }
109 if (field.options) {
110 options[field.name] = field.options;
111 }
112 }
113 });
a14e9d08
CW
114 if ($(changedElement).is('#api-entity') && data.deprecated) {
115 CRM.alert(data.deprecated, entity + ' Deprecated');
116 }
e4176358 117 showFields(required);
b07af612
CW
118 if (action === 'get' || action === 'getsingle') {
119 showReturn();
120 }
0c3a6e64
CW
121 });
122 }
123
b07af612
CW
124 /**
125 * For "get" actions show the "return" options
126 */
127 function showReturn() {
128 $('#api-params').prepend($(returnTpl({})));
b07af612
CW
129 $('#api-return-value').crmSelect2({data: fields, multiple: true});
130 }
131
132 /**
133 * Fetch actions for entity
134 */
2b6e1174
CW
135 function getActions() {
136 if (entity) {
754927e4 137 $('#api-action').addClass('loading');
2b6e1174 138 CRM.api3(entity, 'getactions').done(function(data) {
a14e9d08 139 actions = data;
2b6e1174
CW
140 populateActions();
141 });
142 } else {
a14e9d08 143 actions = {values: ['get']};
2b6e1174
CW
144 populateActions();
145 }
146 }
147
15cbe793
CW
148 function isActionDeprecated(action) {
149 return !!(typeof actions.deprecated === 'object' && actions.deprecated[action]);
150 }
151
a14e9d08 152 function renderAction(option) {
15cbe793 153 return isActionDeprecated(option.id) ? '<span class="strikethrough">' + option.text + '</span>' : option.text;
a14e9d08
CW
154 }
155
b07af612
CW
156 /**
157 * Called after getActions to populate action list
158 * @param el
159 */
2b6e1174 160 function populateActions(el) {
2f929504 161 var val = $('#api-action').val();
754927e4 162 $('#api-action').removeClass('loading').select2({
a14e9d08
CW
163 data: _.transform(actions.values, function(ret, item) {ret.push({text: item, id: item})}),
164 formatSelection: renderAction,
165 formatResult: renderAction
2b6e1174 166 });
2f929504 167 // If previously selected action is not available, set it to 'get' if possible
a14e9d08
CW
168 if (_.indexOf(actions.values, val) < 0) {
169 $('#api-action').select2('val', _.indexOf(actions.values, 'get') < 0 ? actions.values[0] : 'get', true);
170 }
171 }
172
173 function onChangeAction(action) {
15cbe793 174 if (isActionDeprecated(action)) {
a14e9d08 175 CRM.alert(actions.deprecated[action], action + ' deprecated');
2f929504 176 }
2b6e1174
CW
177 }
178
b07af612
CW
179 /**
180 * Called after getfields to show buttons and required fields
181 * @param required
182 */
e4176358 183 function showFields(required) {
e4176358 184 $('#api-params').empty();
8ffa1387 185 $('#api-param-buttons').show();
e4176358
CW
186 if (required.length) {
187 _.each(required, addField);
188 } else {
189 addField();
0c3a6e64
CW
190 }
191 }
192
c275764e 193 /**
fc6a6a51 194 * Render value input as a textfield, option list, entityRef, or hidden,
77099ee0 195 * Depending on selected param name and operator
c275764e 196 */
77099ee0
CW
197 function renderValueField() {
198 var $row = $(this).closest('tr'),
199 name = $('input.api-param-name', $row).val(),
200 operator = $('.api-param-op', $row).val(),
201 operatorType = $.inArray(operator, MULTI) > -1 ? 'multi' : ($.inArray(operator, BOOL) > -1 ? 'bool' : 'single'),
202 $valField = $('input.api-param-value', $row),
203 currentVal = $valField.val();
204 // Boolean fields only have 1 possible value
205 if (operatorType == 'bool') {
206 if ($valField.data('select2')) {
207 $valField.select2('destroy');
208 }
209 $valField.css('visibility', 'hidden').val('1');
210 return;
211 }
212 $valField.css('visibility', '');
fc6a6a51
CW
213 // Option list or entityRef input
214 if ((options[name] || (getFieldData[name] && getFieldData[name].FKApiName)) && $.inArray(operator, TEXT) < 0) {
77099ee0
CW
215 // Reset value before switching to a select from something else
216 if ($(this).is('.api-param-name') || !$valField.data('select2')) {
217 $valField.val('');
218 }
219 // When switching from multi-select to single select
220 else if (operatorType == 'single' && currentVal.indexOf(',') > -1) {
221 $valField.val(currentVal.split(',')[0]);
222 }
fc6a6a51
CW
223 // Select options
224 if (options[name]) {
225
226 $valField.select2({
227 multiple: (operatorType === 'multi'),
228 data: _.map(options[name], function (value, key) {
229 return {id: key, text: value};
230 })
231 });
232 }
233 // EntityRef
234 else {
235 $valField.crmEntityRef({
236 entity: getFieldData[name].FKApiName,
237 select: {multiple: (operatorType === 'multi')}
238 });
239 }
77099ee0 240 return;
e4176358 241 }
77099ee0
CW
242 // Plain text input
243 if ($valField.data('select2')) {
e4176358
CW
244 $valField.select2('destroy');
245 }
e4176358 246 }
0c3a6e64 247
e4176358
CW
248 /**
249 * Attempt to parse a string into a value of the intended type
babf9678
CW
250 * @param val string
251 * @param makeArray bool
e4176358
CW
252 */
253 function evaluate(val, makeArray) {
254 try {
255 if (!val.length) {
77099ee0 256 return makeArray ? [] : '';
e4176358
CW
257 }
258 var first = val.charAt(0),
259 last = val.slice(-1);
260 // Simple types
51f197bf 261 if (val === 'true' || val === 'false' || val === 'null') {
e4176358
CW
262 return eval(val);
263 }
264 // Quoted strings
265 if ((first === '"' || first === "'") && last === first) {
266 return val.slice(1, -1);
0c3a6e64 267 }
e4176358
CW
268 // Parse json
269 if ((first === '[' && last === ']') || (first === '{' && last === '}')) {
270 return eval('(' + val + ')');
271 }
272 // Transform csv to array
77099ee0
CW
273 if (makeArray) {
274 var result = [];
275 $.each(val.split(','), function(k, v) {
276 result.push(evaluate($.trim(v)) || v);
277 });
278 return result;
279 }
280 // Integers - quote any number that starts with 0 to avoid oddities
281 if (!isNaN(val) && val.search(/[^\d]/) < 0 && (val.length === 1 || first !== '0')) {
282 return parseInt(val, 10);
e4176358
CW
283 }
284 // Ok ok it's really a string
285 return val;
286 } catch(e) {
287 // If eval crashed return undefined
288 return undefined;
0c3a6e64 289 }
e4176358 290 }
0c3a6e64 291
e4176358
CW
292 /**
293 * Format value to look like php code
294 * @param val
295 */
296 function phpFormat(val) {
297 var ret = '';
298 if ($.isPlainObject(val)) {
299 $.each(val, function(k, v) {
c275764e 300 ret += (ret ? ', ' : '') + "'" + k + "' => " + phpFormat(v);
e4176358
CW
301 });
302 return 'array(' + ret + ')';
0c3a6e64 303 }
e4176358
CW
304 if ($.isArray(val)) {
305 $.each(val, function(k, v) {
306 ret += (ret ? ', ' : '') + phpFormat(v);
307 });
308 return 'array(' + ret + ')';
309 }
54c512e0 310 return JSON.stringify(val).replace(/\$/g, '\\$');
e4176358
CW
311 }
312
313 /**
314 * Smarty doesn't support array literals so we provide a stub
315 * @param js string
316 */
317 function smartyFormat(js, key) {
318 if (js.indexOf('[') > -1 || js.indexOf('{') > -1) {
2b6e1174 319 smartyStub = true;
e4176358
CW
320 return '$' + key.replace(/[. -]/g, '_');
321 }
322 return js;
323 }
324
c275764e
CW
325 /**
326 * Create the params array from user input
327 * @param e
328 */
e4176358
CW
329 function buildParams(e) {
330 params = {};
331 $('.api-param-checkbox:checked').each(function() {
332 params[this.name] = 1;
333 });
c275764e 334 $('input.api-param-value, input.api-option-value').each(function() {
e4176358 335 var $row = $(this).closest('tr'),
77099ee0 336 op = $('select.api-param-op', $row).val() || '=',
e4176358 337 name = $('input.api-param-name', $row).val(),
77099ee0 338 val = evaluate($(this).val(), $.inArray(op, MULTI) > -1);
b07af612
CW
339
340 // Ignore blank values for the return field
341 if ($(this).is('#api-return-value') && !val) {
342 return;
343 }
8ffa1387
CW
344 // Special syntax for api chaining
345 if (!name && $('select.api-chain-entity', $row).val()) {
346 name = 'api.' + $('select.api-chain-entity', $row).val() + '.' + $('select.api-chain-action', $row).val();
347 }
c275764e
CW
348 // Special handling for options
349 if ($(this).is('.api-option-value')) {
350 op = $('input.api-option-name', $row).val();
351 if (op) {
352 name = 'options';
353 }
354 }
e4176358 355 if (name && val !== undefined) {
c275764e 356 params[name] = op === '=' ? val : (params[name] || {});
e4176358
CW
357 if (op !== '=') {
358 params[name][op] = val;
359 }
babf9678
CW
360 if ($(this).hasClass('crm-error')) {
361 clearError(this);
362 }
e4176358
CW
363 }
364 else if (name && (!e || e.type !== 'keyup')) {
365 setError(this);
366 }
367 });
368 if (entity && action) {
369 formatQuery();
0c3a6e64 370 }
e4176358 371 }
0c3a6e64 372
e4176358
CW
373 function setError(el) {
374 if (!$(el).hasClass('crm-error')) {
babf9678 375 var msg = ts('Syntax error: input should be valid JSON or a quoted string.');
e4176358
CW
376 $(el)
377 .addClass('crm-error')
2b6e1174 378 .css('width', '82%')
babf9678
CW
379 .attr('title', msg)
380 .before('<div class="icon red-icon ui-icon-alert" title="'+msg+'"/>')
381 .tooltip();
e4176358
CW
382 }
383 }
0c3a6e64 384
e4176358
CW
385 function clearError(el) {
386 $(el)
387 .removeClass('crm-error')
388 .attr('title', '')
2b6e1174 389 .css('width', '85%')
babf9678 390 .tooltip('destroy')
e4176358
CW
391 .siblings('.ui-icon-alert').remove();
392 }
0c3a6e64 393
e4176358
CW
394 function formatQuery() {
395 var i = 0, q = {
396 smarty: "{crmAPI var='result' entity='" + entity + "' action='" + action + "'",
397 php: "$result = civicrm_api3('" + entity + "', '" + action + "'",
398 json: "CRM.api3('" + entity + "', '" + action + "'",
2dad2b26
TO
399 drush: "drush cvapi " + entity + '.' + action + ' ',
400 wpcli: "wp cv api " + entity + '.' + action + ' ',
e4176358
CW
401 rest: CRM.config.resourceBase + "extern/rest.php?entity=" + entity + "&action=" + action + "&json=" + JSON.stringify(params) + "&api_key=yoursitekey&key=yourkey"
402 };
2b6e1174 403 smartyStub = false;
e4176358
CW
404 $.each(params, function(key, value) {
405 var js = JSON.stringify(value);
406 if (!i++) {
407 q.php += ", array(\n";
408 q.json += ", {\n";
409 } else {
410 q.json += ",\n";
411 }
412 q.php += " '" + key + "' => " + phpFormat(value) + ",\n";
413 q.json += " \"" + key + '": ' + js;
e4176358 414 q.smarty += ' ' + key + '=' + smartyFormat(js, key);
77099ee0 415 // FIXME: This is not totally correct cli syntax
555d256f 416 q.drush += key + '=' + js + ' ';
2dad2b26 417 q.wpcli += key + '=' + js + ' ';
e4176358
CW
418 });
419 if (i) {
420 q.php += ")";
421 q.json += "\n}";
422 }
423 q.php += ");";
424 q.json += ").done(function(result) {\n // do something\n});";
425 q.smarty += "}\n{foreach from=$result.values item=" + entity.toLowerCase() + "}\n {$" + entity.toLowerCase() + ".some_field}\n{/foreach}";
426 if (action.indexOf('get') < 0) {
427 q.smarty = '{* Smarty API only works with get actions *}';
2b6e1174 428 } else if (smartyStub) {
9405a07b 429 q.smarty = "{* Smarty does not have a syntax for array literals; assign complex variables from php *}\n" + q.smarty;
e4176358
CW
430 }
431 $.each(q, function(type, val) {
cd77ffa6 432 $('#api-' + type).removeClass('prettyprinted').text(val);
e4176358 433 });
cd77ffa6 434 prettyPrint();
e4176358 435 }
0c3a6e64 436
e4176358
CW
437 function submit(e) {
438 e.preventDefault();
439 if (!entity || !action) {
2b6e1174 440 alert(ts('Select an entity.'));
e4176358
CW
441 return;
442 }
2f929504 443 if (action.indexOf('get') < 0 && action != 'check') {
e4176358
CW
444 var msg = action === 'delete' ? ts('This will delete data from CiviCRM. Are you sure?') : ts('This will write to the database. Continue?');
445 CRM.confirm({title: ts('Confirm %1', {1: action}), message: msg}).on('crmConfirm:yes', execute);
0c3a6e64 446 } else {
e4176358 447 execute();
0c3a6e64 448 }
0c3a6e64
CW
449 }
450
77099ee0
CW
451 /**
452 * Execute api call and display the results
453 * Note: We have to manually execute the ajax in order to add the secret extra "prettyprint" param
454 */
e4176358
CW
455 function execute() {
456 $('#api-result').html('<div class="crm-loading-element"></div>');
457 $.ajax({
458 url: CRM.url('civicrm/ajax/rest'),
459 data: {
460 entity: entity,
461 action: action,
462 prettyprint: 1,
463 json: JSON.stringify(params)
464 },
465 type: action.indexOf('get') < 0 ? 'POST' : 'GET',
466 dataType: 'text'
467 }).done(function(text) {
2b6e1174 468 $('#api-result').addClass('prettyprint').removeClass('prettyprinted').text(text);
cd77ffa6 469 prettyPrint();
e4176358 470 });
0c3a6e64 471 }
e4176358
CW
472
473 $(document).ready(function() {
a14e9d08
CW
474 $('#api-entity').crmSelect2({
475 // Add strikethough class to selection to indicate deprecated apis
476 formatSelection: function(option) {
477 return $(option.element).hasClass('strikethrough') ? '<span class="strikethrough">' + option.text + '</span>' : option.text;
478 }
479 });
e4176358
CW
480 $('form#api-explorer')
481 .on('change', '#api-entity, #api-action', function() {
482 entity = $('#api-entity').val();
a14e9d08 483 action = $('#api-action').val();
2b6e1174 484 if ($(this).is('#api-entity')) {
2b6e1174 485 getActions();
a14e9d08
CW
486 } else {
487 onChangeAction(action);
2b6e1174 488 }
e4176358
CW
489 if (entity && action) {
490 $('#api-params').html('<tr><td colspan="4" class="crm-loading-element"></td></tr>');
491 $('#api-params-table thead').show();
a14e9d08 492 getFields(this);
e4176358
CW
493 buildParams();
494 } else {
495 $('#api-params, #api-generated pre').empty();
8ffa1387 496 $('#api-param-buttons, #api-params-table thead').hide();
e4176358
CW
497 }
498 })
2b6e1174 499 .on('change keyup', 'input.api-input, #api-params select', buildParams)
e4176358
CW
500 .on('submit', submit);
501 $('#api-params')
77099ee0
CW
502 .on('change', 'input.api-param-name, select.api-param-op', renderValueField)
503 .on('change', 'input.api-param-name, .api-option-name', function() {
c275764e
CW
504 if ($(this).val() === '-') {
505 $(this).select2('destroy');
506 $(this).val('').focus();
507 }
508 })
e4176358
CW
509 .on('click', '.api-param-remove', function(e) {
510 e.preventDefault();
511 $(this).closest('tr').remove();
512 buildParams();
513 });
514 $('#api-params-add').on('click', function(e) {
8ffa1387 515 e.preventDefault();
e4176358 516 addField();
8ffa1387 517 });
c275764e
CW
518 $('#api-option-add').on('click', function(e) {
519 e.preventDefault();
520 addOptionField();
521 });
8ffa1387 522 $('#api-chain-add').on('click', function(e) {
e4176358 523 e.preventDefault();
8ffa1387 524 addChainField();
e4176358
CW
525 });
526 $('#api-entity').change();
0c3a6e64 527 });
e4176358 528}(CRM.$, CRM._));