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