Commit | Line | Data |
---|---|---|
25523059 CW |
1 | (function(angular, $, _) { |
2 | "use strict"; | |
3 | ||
493f83d4 | 4 | angular.module('crmSearchAdmin').component('crmSearchAdmin', { |
25523059 | 5 | bindings: { |
2894db84 | 6 | savedSearch: '<' |
25523059 | 7 | }, |
493f83d4 | 8 | templateUrl: '~/crmSearchAdmin/crmSearchAdmin.html', |
7d527c18 | 9 | controller: function($scope, $element, $location, $timeout, crmApi4, dialogService, searchMeta) { |
33e81cf6 | 10 | var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'), |
3dfd2744 | 11 | ctrl = this, |
02c7fc51 | 12 | afformLoad, |
3dfd2744 | 13 | fieldsForJoinGetters = {}; |
5fcd63f4 CW |
14 | |
15 | this.DEFAULT_AGGREGATE_FN = 'GROUP_CONCAT'; | |
02c7fc51 CW |
16 | this.afformEnabled = CRM.crmSearchAdmin.afformEnabled; |
17 | this.afformAdminEnabled = CRM.crmSearchAdmin.afformAdminEnabled; | |
ecb9d1eb | 18 | this.displayTypes = _.indexBy(CRM.crmSearchAdmin.displayTypes, 'id'); |
5c952e51 CW |
19 | this.searchDisplayPath = CRM.url('civicrm/search'); |
20 | this.afformPath = CRM.url('civicrm/admin/afform'); | |
25523059 | 21 | |
266e8deb CW |
22 | $scope.controls = {tab: 'compose', joinType: 'LEFT'}; |
23 | $scope.joinTypes = [ | |
24 | {k: 'LEFT', v: ts('With (optional)')}, | |
25 | {k: 'INNER', v: ts('With (required)')}, | |
26 | {k: 'EXCLUDE', v: ts('Without')}, | |
27 | ]; | |
a019205f CW |
28 | $scope.getEntity = searchMeta.getEntity; |
29 | $scope.getField = searchMeta.getField; | |
25523059 CW |
30 | this.perm = { |
31 | editGroups: CRM.checkPerm('edit groups') | |
32 | }; | |
33 | ||
2894db84 CW |
34 | this.$onInit = function() { |
35 | this.entityTitle = searchMeta.getEntity(this.savedSearch.api_entity).title_plural; | |
36 | ||
f9197b41 | 37 | this.savedSearch.displays = this.savedSearch.displays || []; |
44402a2e | 38 | this.savedSearch.groups = this.savedSearch.groups || []; |
94d356ed | 39 | this.savedSearch.tag_id = this.savedSearch.tag_id || []; |
44402a2e | 40 | this.groupExists = !!this.savedSearch.groups.length; |
f9197b41 | 41 | |
b40e49df | 42 | if (!this.savedSearch.id) { |
2b02bf9b CW |
43 | var defaults = { |
44 | version: 4, | |
45 | select: getDefaultSelect(), | |
46 | orderBy: {}, | |
47 | where: [], | |
48 | }; | |
49 | _.each(['groupBy', 'join', 'having'], function(param) { | |
50 | if (ctrl.paramExists(param)) { | |
51 | defaults[param] = []; | |
52 | } | |
53 | }); | |
54 | ||
b40e49df CW |
55 | $scope.$bindToRoute({ |
56 | param: 'params', | |
57 | expr: '$ctrl.savedSearch.api_params', | |
b1603dbd | 58 | deep: true, |
2b02bf9b | 59 | default: defaults |
b40e49df | 60 | }); |
2894db84 CW |
61 | } |
62 | ||
94d356ed | 63 | $scope.mainEntitySelect = searchMeta.getPrimaryAndSecondaryEntitySelect(); |
4736288b | 64 | |
2894db84 CW |
65 | $scope.$watchCollection('$ctrl.savedSearch.api_params.select', onChangeSelect); |
66 | ||
44402a2e CW |
67 | $scope.$watch('$ctrl.savedSearch', onChangeAnything, true); |
68 | ||
2badf248 CW |
69 | // After watcher runs for the first time and messes up the status, set it correctly |
70 | $timeout(function() { | |
71 | $scope.status = ctrl.savedSearch && ctrl.savedSearch.id ? 'saved' : 'unsaved'; | |
72 | }); | |
44402a2e | 73 | |
2894db84 | 74 | loadFieldOptions(); |
02c7fc51 | 75 | loadAfforms(); |
2894db84 | 76 | }; |
25523059 | 77 | |
44402a2e CW |
78 | function onChangeAnything() { |
79 | $scope.status = 'unsaved'; | |
80 | } | |
81 | ||
82 | this.save = function() { | |
2badf248 CW |
83 | if (!validate()) { |
84 | return; | |
85 | } | |
44402a2e CW |
86 | $scope.status = 'saving'; |
87 | var params = _.cloneDeep(ctrl.savedSearch), | |
88 | apiCalls = {}, | |
89 | chain = {}; | |
90 | if (ctrl.groupExists) { | |
91 | chain.groups = ['Group', 'save', {defaults: {saved_search_id: '$id'}, records: params.groups}]; | |
92 | delete params.groups; | |
93 | } else if (params.id) { | |
94 | apiCalls.deleteGroup = ['Group', 'delete', {where: [['saved_search_id', '=', params.id]]}]; | |
95 | } | |
5af55119 | 96 | _.remove(params.displays, {trashed: true}); |
44402a2e CW |
97 | if (params.displays && params.displays.length) { |
98 | chain.displays = ['SearchDisplay', 'replace', {where: [['saved_search_id', '=', '$id']], records: params.displays}]; | |
99 | } else if (params.id) { | |
100 | apiCalls.deleteDisplays = ['SearchDisplay', 'delete', {where: [['saved_search_id', '=', params.id]]}]; | |
101 | } | |
102 | delete params.displays; | |
94d356ed CW |
103 | if (params.tag_id && params.tag_id.length) { |
104 | chain.tag_id = ['EntityTag', 'replace', { | |
105 | where: [['entity_id', '=', '$id'], ['entity_table', '=', 'civicrm_saved_search']], | |
106 | records: _.transform(params.tag_id, function(records, id) {records.push({tag_id: id});}) | |
107 | }]; | |
108 | } else if (params.id) { | |
109 | chain.tag_id = ['EntityTag', 'delete', { | |
110 | where: [['entity_id', '=', '$id'], ['entity_table', '=', 'civicrm_saved_search']] | |
111 | }]; | |
112 | } | |
113 | delete params.tag_id; | |
44402a2e CW |
114 | apiCalls.saved = ['SavedSearch', 'save', {records: [params], chain: chain}, 0]; |
115 | crmApi4(apiCalls).then(function(results) { | |
b40e49df CW |
116 | // After saving a new search, redirect to the edit url |
117 | if (!ctrl.savedSearch.id) { | |
118 | $location.url('edit/' + results.saved.id); | |
119 | } | |
2badf248 CW |
120 | // Set new status to saved unless the user changed something in the interim |
121 | var newStatus = $scope.status === 'unsaved' ? 'unsaved' : 'saved'; | |
2badf248 CW |
122 | if (results.saved.groups && results.saved.groups.length) { |
123 | ctrl.savedSearch.groups[0].id = results.saved.groups[0].id; | |
44402a2e | 124 | } |
2badf248 CW |
125 | ctrl.savedSearch.displays = results.saved.displays || []; |
126 | // Wait until after onChangeAnything to update status | |
127 | $timeout(function() { | |
128 | $scope.status = newStatus; | |
129 | }); | |
44402a2e CW |
130 | }); |
131 | }; | |
132 | ||
25523059 | 133 | this.paramExists = function(param) { |
2894db84 | 134 | return _.includes(searchMeta.getEntity(ctrl.savedSearch.api_entity).params, param); |
25523059 CW |
135 | }; |
136 | ||
f9197b41 | 137 | this.addDisplay = function(type) { |
6f0c7312 | 138 | var count = _.filter(ctrl.savedSearch.displays, {type: type}).length; |
f9197b41 | 139 | ctrl.savedSearch.displays.push({ |
44402a2e | 140 | type: type, |
6f0c7312 | 141 | label: ctrl.displayTypes[type].label + (count ? ' ' + (++count) : '') |
f9197b41 | 142 | }); |
4b01551f CW |
143 | $scope.selectTab('display_' + (ctrl.savedSearch.displays.length - 1)); |
144 | }; | |
145 | ||
146 | this.removeDisplay = function(index) { | |
147 | var display = ctrl.savedSearch.displays[index]; | |
148 | if (display.id) { | |
149 | display.trashed = !display.trashed; | |
2badf248 CW |
150 | if ($scope.controls.tab === ('display_' + index) && display.trashed) { |
151 | $scope.selectTab('compose'); | |
152 | } else if (!display.trashed) { | |
153 | $scope.selectTab('display_' + index); | |
154 | } | |
02c7fc51 CW |
155 | if (display.trashed && afformLoad) { |
156 | afformLoad.then(function() { | |
5c952e51 CW |
157 | var displayForms = _.filter(ctrl.afforms, function(form) { |
158 | return _.includes(form.displays, ctrl.savedSearch.name + '.' + display.name); | |
159 | }); | |
160 | if (displayForms.length) { | |
161 | var msg = displayForms.length === 1 ? | |
162 | ts('Form "%1" will be deleted if the embedded display "%2" is deleted.', {1: displayForms[0].title, 2: display.label}) : | |
163 | ts('%1 forms will be deleted if the embedded display "%2" is deleted.', {1: displayForms.length, 2: display.label}); | |
02c7fc51 CW |
164 | CRM.alert(msg, ts('Display embedded'), 'alert'); |
165 | } | |
166 | }); | |
167 | } | |
4b01551f CW |
168 | } else { |
169 | $scope.selectTab('compose'); | |
170 | ctrl.savedSearch.displays.splice(index, 1); | |
171 | } | |
172 | }; | |
173 | ||
174 | this.addGroup = function() { | |
44402a2e | 175 | ctrl.savedSearch.groups.push({ |
4b01551f CW |
176 | title: '', |
177 | description: '', | |
178 | visibility: 'User and User Admin Only', | |
179 | group_type: [] | |
44402a2e | 180 | }); |
4b01551f CW |
181 | ctrl.groupExists = true; |
182 | $scope.selectTab('group'); | |
183 | }; | |
184 | ||
185 | $scope.selectTab = function(tab) { | |
186 | if (tab === 'group') { | |
a019205f | 187 | loadFieldOptions('Group'); |
4b01551f CW |
188 | $scope.smartGroupColumns = searchMeta.getSmartGroupColumns(ctrl.savedSearch.api_entity, ctrl.savedSearch.api_params); |
189 | var smartGroupColumns = _.map($scope.smartGroupColumns, 'id'); | |
190 | if (smartGroupColumns.length && !_.includes(smartGroupColumns, ctrl.savedSearch.api_params.select[0])) { | |
191 | ctrl.savedSearch.api_params.select.unshift(smartGroupColumns[0]); | |
192 | } | |
193 | } | |
194 | ctrl.savedSearch.api_params.select = _.uniq(ctrl.savedSearch.api_params.select); | |
195 | $scope.controls.tab = tab; | |
196 | }; | |
197 | ||
198 | this.removeGroup = function() { | |
199 | ctrl.groupExists = !ctrl.groupExists; | |
a019205f | 200 | $scope.status = 'unsaved'; |
44402a2e CW |
201 | if (!ctrl.groupExists && (!ctrl.savedSearch.groups.length || !ctrl.savedSearch.groups[0].id)) { |
202 | ctrl.savedSearch.groups.length = 0; | |
4b01551f | 203 | } |
2badf248 CW |
204 | if ($scope.controls.tab === 'group') { |
205 | $scope.selectTab('compose'); | |
206 | } | |
f9197b41 CW |
207 | }; |
208 | ||
994168e1 CW |
209 | function addNum(name, num) { |
210 | return name + (num < 10 ? '_0' : '_') + num; | |
211 | } | |
212 | ||
213 | function getExistingJoins() { | |
214 | return _.transform(ctrl.savedSearch.api_params.join || [], function(joins, join) { | |
215 | joins[join[0].split(' AS ')[1]] = searchMeta.getJoin(join[0]); | |
216 | }, {}); | |
217 | } | |
218 | ||
4f0729ed CW |
219 | $scope.getJoin = searchMeta.getJoin; |
220 | ||
25523059 | 221 | $scope.getJoinEntities = function() { |
994168e1 CW |
222 | var existingJoins = getExistingJoins(); |
223 | ||
224 | function addEntityJoins(entity, stack, baseEntity) { | |
225 | return _.transform(CRM.crmSearchAdmin.joins[entity], function(joinEntities, join) { | |
226 | var num = 0; | |
227 | // Add all joins that don't just point directly back to the original entity | |
228 | if (!(baseEntity === join.entity && !join.multi)) { | |
229 | do { | |
230 | appendJoin(joinEntities, join, ++num, stack, entity); | |
231 | } while (addNum((stack ? stack + '_' : '') + join.alias, num) in existingJoins); | |
232 | } | |
233 | }, []); | |
234 | } | |
235 | ||
236 | function appendJoin(collection, join, num, stack, baseEntity) { | |
237 | var alias = addNum((stack ? stack + '_' : '') + join.alias, num), | |
238 | opt = { | |
239 | id: join.entity + ' AS ' + alias, | |
4f0729ed | 240 | description: join.description, |
994168e1 CW |
241 | text: join.label + (num > 1 ? ' ' + num : ''), |
242 | icon: searchMeta.getEntity(join.entity).icon, | |
243 | disabled: alias in existingJoins | |
244 | }; | |
245 | if (alias in existingJoins) { | |
246 | opt.children = addEntityJoins(join.entity, (stack ? stack + '_' : '') + alias, baseEntity); | |
25523059 | 247 | } |
994168e1 CW |
248 | collection.push(opt); |
249 | } | |
250 | ||
251 | return {results: addEntityJoins(ctrl.savedSearch.api_entity)}; | |
25523059 CW |
252 | }; |
253 | ||
7c219eb3 CW |
254 | this.addJoin = function(value) { |
255 | if (value) { | |
256 | ctrl.savedSearch.api_params.join = ctrl.savedSearch.api_params.join || []; | |
257 | var join = searchMeta.getJoin(value), | |
258 | entity = searchMeta.getEntity(join.entity), | |
259 | params = [value, $scope.controls.joinType || 'LEFT']; | |
260 | _.each(_.cloneDeep(join.conditions), function(condition) { | |
261 | params.push(condition); | |
262 | }); | |
263 | _.each(_.cloneDeep(join.defaults), function(condition) { | |
264 | params.push(condition); | |
265 | }); | |
266 | ctrl.savedSearch.api_params.join.push(params); | |
267 | if (entity.label_field && $scope.controls.joinType !== 'EXCLUDE') { | |
268 | ctrl.savedSearch.api_params.select.push(join.alias + '.' + entity.label_field); | |
25523059 | 269 | } |
7c219eb3 CW |
270 | loadFieldOptions(); |
271 | } | |
25523059 CW |
272 | }; |
273 | ||
c15d4a0c CW |
274 | // Remove an explicit join + all SELECT, WHERE & other JOINs that use it |
275 | this.removeJoin = function(index) { | |
276 | var alias = searchMeta.getJoin(ctrl.savedSearch.api_params.join[index][0]).alias; | |
277 | ctrl.clearParam('join', index); | |
266e8deb CW |
278 | removeJoinStuff(alias); |
279 | }; | |
280 | ||
281 | function removeJoinStuff(alias) { | |
c15d4a0c | 282 | _.remove(ctrl.savedSearch.api_params.select, function(item) { |
133f6a11 CW |
283 | var pattern = new RegExp('\\b' + alias + '\\.'); |
284 | return pattern.test(item.split(' AS ')[0]); | |
c15d4a0c CW |
285 | }); |
286 | _.remove(ctrl.savedSearch.api_params.where, function(clause) { | |
287 | return clauseUsesJoin(clause, alias); | |
288 | }); | |
289 | _.eachRight(ctrl.savedSearch.api_params.join, function(item, i) { | |
266e8deb CW |
290 | var joinAlias = searchMeta.getJoin(item[0]).alias; |
291 | if (joinAlias !== alias && joinAlias.indexOf(alias) === 0) { | |
c15d4a0c CW |
292 | ctrl.removeJoin(i); |
293 | } | |
294 | }); | |
266e8deb CW |
295 | } |
296 | ||
297 | this.changeJoinType = function(join) { | |
298 | if (join[1] === 'EXCLUDE') { | |
299 | removeJoinStuff(searchMeta.getJoin(join[0]).alias); | |
300 | } | |
c15d4a0c CW |
301 | }; |
302 | ||
25523059 | 303 | $scope.changeGroupBy = function(idx) { |
1cb03463 | 304 | // When clearing a selection |
2894db84 | 305 | if (!ctrl.savedSearch.api_params.groupBy[idx]) { |
25523059 CW |
306 | ctrl.clearParam('groupBy', idx); |
307 | } | |
1cb03463 | 308 | reconcileAggregateColumns(); |
25523059 CW |
309 | }; |
310 | ||
1cb03463 CW |
311 | function reconcileAggregateColumns() { |
312 | _.each(ctrl.savedSearch.api_params.select, function(col, pos) { | |
313 | var info = searchMeta.parseExpr(col), | |
7891ca94 | 314 | fieldExpr = (_.findWhere(info.args, {type: 'field'}) || {}).value; |
1cb03463 CW |
315 | if (ctrl.canAggregate(col)) { |
316 | // Ensure all non-grouped columns are aggregated if using GROUP BY | |
317 | if (!info.fn || info.fn.category !== 'aggregate') { | |
2fe33e6c | 318 | ctrl.savedSearch.api_params.select[pos] = ctrl.DEFAULT_AGGREGATE_FN + '(DISTINCT ' + fieldExpr + ') AS ' + ctrl.DEFAULT_AGGREGATE_FN + '_' + fieldExpr.replace(/[.:]/g, '_'); |
1cb03463 CW |
319 | } |
320 | } else { | |
321 | // Remove aggregate functions when no grouping | |
322 | if (info.fn && info.fn.category === 'aggregate') { | |
323 | ctrl.savedSearch.api_params.select[pos] = fieldExpr; | |
324 | } | |
325 | } | |
326 | }); | |
327 | } | |
328 | ||
c15d4a0c CW |
329 | function clauseUsesJoin(clause, alias) { |
330 | if (clause[0].indexOf(alias + '.') === 0) { | |
331 | return true; | |
332 | } | |
333 | if (_.isArray(clause[1])) { | |
334 | return clause[1].some(function(subClause) { | |
335 | return clauseUsesJoin(subClause, alias); | |
336 | }); | |
337 | } | |
338 | return false; | |
339 | } | |
340 | ||
341 | // Returns true if a clause contains one of the | |
342 | function clauseUsesFields(clause, fields) { | |
c15d4a0c CW |
343 | if (!fields || !fields.length) { |
344 | return false; | |
345 | } | |
346 | if (_.includes(fields, clause[0])) { | |
347 | return true; | |
348 | } | |
349 | if (_.isArray(clause[1])) { | |
350 | return clause[1].some(function(subClause) { | |
351 | return clauseUsesField(subClause, fields); | |
352 | }); | |
353 | } | |
354 | return false; | |
355 | } | |
356 | ||
2badf248 CW |
357 | function validate() { |
358 | var errors = [], | |
359 | errorEl, | |
360 | label, | |
361 | tab; | |
362 | if (!ctrl.savedSearch.label) { | |
363 | errorEl = '#crm-saved-search-label'; | |
364 | label = ts('Search Label'); | |
365 | errors.push(ts('%1 is a required field.', {1: label})); | |
366 | } | |
367 | if (ctrl.groupExists && !ctrl.savedSearch.groups[0].title) { | |
368 | errorEl = '#crm-search-admin-group-title'; | |
369 | label = ts('Group Title'); | |
370 | errors.push(ts('%1 is a required field.', {1: label})); | |
371 | tab = 'group'; | |
372 | } | |
373 | _.each(ctrl.savedSearch.displays, function(display, index) { | |
374 | if (!display.trashed && !display.label) { | |
375 | errorEl = '#crm-search-admin-display-label'; | |
376 | label = ts('Display Label'); | |
377 | errors.push(ts('%1 is a required field.', {1: label})); | |
378 | tab = 'display_' + index; | |
379 | } | |
380 | }); | |
381 | if (errors.length) { | |
382 | if (tab) { | |
383 | $scope.selectTab(tab); | |
384 | } | |
385 | $(errorEl).crmError(errors.join('<br>'), ts('Error Saving'), {expires: 5000}); | |
386 | } | |
387 | return !errors.length; | |
388 | } | |
389 | ||
7c219eb3 CW |
390 | this.addParam = function(name, value) { |
391 | if (value && !_.contains(ctrl.savedSearch.api_params[name], value)) { | |
392 | ctrl.savedSearch.api_params[name].push(value); | |
1cb03463 CW |
393 | // This needs to be called when adding a field as well as changing groupBy |
394 | reconcileAggregateColumns(); | |
25523059 | 395 | } |
25523059 CW |
396 | }; |
397 | ||
398 | // Deletes an item from an array param | |
399 | this.clearParam = function(name, idx) { | |
173405e2 CW |
400 | if (name === 'select') { |
401 | // Function selectors use `ng-repeat` with `track by $index` so must be refreshed when splicing the array | |
402 | ctrl.hideFuncitons(); | |
403 | } | |
2894db84 | 404 | ctrl.savedSearch.api_params[name].splice(idx, 1); |
25523059 CW |
405 | }; |
406 | ||
173405e2 CW |
407 | this.hideFuncitons = function() { |
408 | $scope.controls.showFunctions = false; | |
409 | }; | |
410 | ||
25523059 | 411 | function onChangeSelect(newSelect, oldSelect) { |
57fa023b CW |
412 | // When removing a column from SELECT, also remove from ORDER BY & HAVING |
413 | _.each(_.difference(oldSelect, newSelect), function(col) { | |
414 | col = _.last(col.split(' AS ')); | |
2894db84 | 415 | delete ctrl.savedSearch.api_params.orderBy[col]; |
c15d4a0c | 416 | _.remove(ctrl.savedSearch.api_params.having, function(clause) { |
57fa023b | 417 | return clauseUsesFields(clause, [col]); |
c15d4a0c | 418 | }); |
57fa023b | 419 | }); |
3b801069 CW |
420 | } |
421 | ||
03b55607 | 422 | this.getFieldLabel = searchMeta.getDefaultLabel; |
25523059 CW |
423 | |
424 | // Is a column eligible to use an aggregate function? | |
425 | this.canAggregate = function(col) { | |
f9cf8797 CW |
426 | // If the query does not use grouping, never |
427 | if (!ctrl.savedSearch.api_params.groupBy.length) { | |
428 | return false; | |
429 | } | |
7891ca94 CW |
430 | var arg = _.findWhere(searchMeta.parseExpr(col).args, {type: 'field'}) || {}; |
431 | // If the column is not a database field, no | |
432 | if (!arg.field || !arg.field.entity || arg.field.type !== 'Field') { | |
433 | return false; | |
434 | } | |
25523059 | 435 | // If the column is used for a groupBy, no |
7891ca94 | 436 | if (ctrl.savedSearch.api_params.groupBy.indexOf(arg.path) > -1) { |
25523059 CW |
437 | return false; |
438 | } | |
b7d8183b | 439 | // If the entity this column belongs to is being grouped by primary key, then also no |
7891ca94 CW |
440 | var idField = searchMeta.getEntity(arg.field.entity).primary_key[0]; |
441 | return ctrl.savedSearch.api_params.groupBy.indexOf(arg.prefix + idField) < 0; | |
25523059 CW |
442 | }; |
443 | ||
25523059 | 444 | $scope.fieldsForGroupBy = function() { |
a1415a02 | 445 | return {results: ctrl.getAllFields('', ['Field', 'Custom'], function(key) { |
2894db84 | 446 | return _.contains(ctrl.savedSearch.api_params.groupBy, key); |
25523059 CW |
447 | }) |
448 | }; | |
449 | }; | |
450 | ||
3dfd2744 | 451 | function getFieldsForJoin(joinEntity) { |
ba149e21 | 452 | return {results: ctrl.getAllFields(':name', ['Field'], null, joinEntity)}; |
3dfd2744 CW |
453 | } |
454 | ||
ba149e21 | 455 | // @return {function} |
3dfd2744 CW |
456 | $scope.fieldsForJoin = function(joinEntity) { |
457 | if (!fieldsForJoinGetters[joinEntity]) { | |
458 | fieldsForJoinGetters[joinEntity] = _.wrap(joinEntity, getFieldsForJoin); | |
459 | } | |
460 | return fieldsForJoinGetters[joinEntity]; | |
461 | }; | |
462 | ||
25523059 | 463 | $scope.fieldsForWhere = function() { |
9e827e8e | 464 | return {results: ctrl.getAllFields(':name')}; |
25523059 CW |
465 | }; |
466 | ||
467 | $scope.fieldsForHaving = function() { | |
9e827e8e | 468 | return {results: ctrl.getSelectFields()}; |
25523059 CW |
469 | }; |
470 | ||
4b01551f | 471 | // Sets the default select clause based on commonly-named fields |
25523059 | 472 | function getDefaultSelect() { |
b0f5b67a CW |
473 | var entity = searchMeta.getEntity(ctrl.savedSearch.api_entity); |
474 | return _.transform(entity.fields, function(defaultSelect, field) { | |
475 | if (field.name === 'id' || field.name === entity.label_field) { | |
476 | defaultSelect.push(field.name); | |
4b01551f | 477 | } |
c419e6ed | 478 | }); |
25523059 CW |
479 | } |
480 | ||
a1415a02 | 481 | this.getAllFields = function(suffix, allowedTypes, disabledIf, topJoin) { |
9e827e8e | 482 | disabledIf = disabledIf || _.noop; |
7d527c18 CW |
483 | |
484 | function formatEntityFields(entityName, join) { | |
4f0729ed CW |
485 | var prefix = join ? join.alias + '.' : '', |
486 | result = []; | |
487 | ||
4f0729ed CW |
488 | // Add extra searchable fields from bridge entity |
489 | if (join && join.bridge) { | |
7d527c18 | 490 | formatFields(_.filter(searchMeta.getEntity(join.bridge).fields, function(field) { |
07e7a46b | 491 | return (field.name !== 'id' && field.name !== 'entity_id' && field.name !== 'entity_table' && !field.fk_entity); |
7d527c18 | 492 | }), result, prefix); |
4f0729ed CW |
493 | } |
494 | ||
7d527c18 CW |
495 | formatFields(searchMeta.getEntity(entityName).fields, result, prefix); |
496 | return result; | |
497 | } | |
498 | ||
499 | function formatFields(fields, result, prefix) { | |
500 | prefix = typeof prefix === 'undefined' ? '' : prefix; | |
501 | _.each(fields, function(field) { | |
502 | var item = { | |
503 | id: prefix + field.name + (field.options ? suffix : ''), | |
504 | text: field.label, | |
505 | description: field.description | |
506 | }; | |
507 | if (disabledIf(item.id)) { | |
508 | item.disabled = true; | |
509 | } | |
510 | if (!allowedTypes || _.includes(allowedTypes, field.type)) { | |
511 | result.push(item); | |
512 | } | |
513 | }); | |
4f0729ed | 514 | return result; |
25523059 CW |
515 | } |
516 | ||
2894db84 | 517 | var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity), |
3dfd2744 CW |
518 | joinEntities = _.map(ctrl.savedSearch.api_params.join, 0), |
519 | result = []; | |
520 | ||
521 | function addJoin(join) { | |
522 | var joinInfo = searchMeta.getJoin(join), | |
4f0729ed | 523 | joinEntity = searchMeta.getEntity(joinInfo.entity); |
25523059 | 524 | result.push({ |
4f0729ed CW |
525 | text: joinInfo.label, |
526 | description: joinInfo.description, | |
25523059 | 527 | icon: joinEntity.icon, |
7d527c18 | 528 | children: formatEntityFields(joinEntity.name, joinInfo) |
25523059 | 529 | }); |
3dfd2744 CW |
530 | } |
531 | ||
532 | // Place specified join at top of list | |
533 | if (topJoin) { | |
534 | addJoin(topJoin); | |
535 | _.pull(joinEntities, topJoin); | |
536 | } | |
537 | ||
538 | result.push({ | |
539 | text: mainEntity.title_plural, | |
540 | icon: mainEntity.icon, | |
7d527c18 | 541 | children: formatEntityFields(ctrl.savedSearch.api_entity) |
25523059 | 542 | }); |
7d527c18 CW |
543 | |
544 | // Include SearchKit's pseudo-fields if specifically requested | |
545 | if (allowedTypes && _.includes(allowedTypes, 'Pseudo')) { | |
546 | result.push({ | |
547 | text: ts('Extra'), | |
548 | icon: 'fa-gear', | |
549 | children: formatFields(CRM.crmSearchAdmin.pseudoFields, []) | |
550 | }); | |
551 | } | |
552 | ||
3dfd2744 | 553 | _.each(joinEntities, addJoin); |
25523059 | 554 | return result; |
9e827e8e CW |
555 | }; |
556 | ||
557 | this.getSelectFields = function(disabledIf) { | |
558 | disabledIf = disabledIf || _.noop; | |
559 | return _.transform(ctrl.savedSearch.api_params.select, function(fields, name) { | |
560 | var info = searchMeta.parseExpr(name); | |
561 | var item = { | |
2b055918 | 562 | id: info.alias, |
9e827e8e | 563 | text: ctrl.getFieldLabel(name), |
7891ca94 | 564 | description: info.fn ? info.fn.description : info.args[0].field && info.args[0].field.description |
9e827e8e CW |
565 | }; |
566 | if (disabledIf(item.id)) { | |
567 | item.disabled = true; | |
568 | } | |
569 | fields.push(item); | |
570 | }); | |
571 | }; | |
25523059 | 572 | |
7d527c18 CW |
573 | this.isPseudoField = function(name) { |
574 | return _.findIndex(CRM.crmSearchAdmin.pseudoFields, {name: name}) >= 0; | |
575 | }; | |
576 | ||
25523059 CW |
577 | /** |
578 | * Fetch pseudoconstants for main entity + joined entities | |
579 | * | |
580 | * Sets an optionsLoaded property on each entity to avoid duplicate requests | |
a019205f CW |
581 | * |
582 | * @var string entity - optional additional entity to load | |
25523059 | 583 | */ |
a019205f | 584 | function loadFieldOptions(entity) { |
2894db84 | 585 | var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity), |
25523059 CW |
586 | entities = {}; |
587 | ||
588 | function enqueue(entity) { | |
589 | entity.optionsLoaded = false; | |
590 | entities[entity.name] = [entity.name, 'getFields', { | |
22601c92 | 591 | loadOptions: ['id', 'name', 'label', 'description', 'color', 'icon'], |
25523059 CW |
592 | where: [['options', '!=', false]], |
593 | select: ['options'] | |
594 | }, {name: 'options'}]; | |
595 | } | |
596 | ||
597 | if (typeof mainEntity.optionsLoaded === 'undefined') { | |
598 | enqueue(mainEntity); | |
599 | } | |
a019205f CW |
600 | |
601 | // Optional additional entity | |
602 | if (entity && typeof searchMeta.getEntity(entity).optionsLoaded === 'undefined') { | |
603 | enqueue(searchMeta.getEntity(entity)); | |
604 | } | |
605 | ||
2894db84 | 606 | _.each(ctrl.savedSearch.api_params.join, function(join) { |
4f0729ed CW |
607 | var joinInfo = searchMeta.getJoin(join[0]), |
608 | joinEntity = searchMeta.getEntity(joinInfo.entity), | |
609 | bridgeEntity = joinInfo.bridge ? searchMeta.getEntity(joinInfo.bridge) : null; | |
25523059 CW |
610 | if (typeof joinEntity.optionsLoaded === 'undefined') { |
611 | enqueue(joinEntity); | |
612 | } | |
4f0729ed CW |
613 | if (bridgeEntity && typeof bridgeEntity.optionsLoaded === 'undefined') { |
614 | enqueue(bridgeEntity); | |
615 | } | |
25523059 CW |
616 | }); |
617 | if (!_.isEmpty(entities)) { | |
618 | crmApi4(entities).then(function(results) { | |
619 | _.each(results, function(fields, entityName) { | |
620 | var entity = searchMeta.getEntity(entityName); | |
621 | _.each(fields, function(options, fieldName) { | |
622 | _.find(entity.fields, {name: fieldName}).options = options; | |
623 | }); | |
624 | entity.optionsLoaded = true; | |
625 | }); | |
626 | }); | |
627 | } | |
628 | } | |
629 | ||
957358aa | 630 | // Build a list of all possible links to main entity & join entities |
9446fbaa | 631 | // @return {Array} |
957358aa CW |
632 | this.buildLinks = function() { |
633 | function addTitle(link, entityName) { | |
9446fbaa | 634 | link.text = link.text.replace('%1', entityName); |
957358aa CW |
635 | } |
636 | ||
637 | // Links to main entity | |
638 | var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity), | |
9446fbaa | 639 | links = _.cloneDeep(mainEntity.links || []); |
957358aa CW |
640 | _.each(links, function(link) { |
641 | link.join = ''; | |
642 | addTitle(link, mainEntity.title); | |
643 | }); | |
644 | // Links to explicitly joined entities | |
645 | _.each(ctrl.savedSearch.api_params.join, function(joinClause) { | |
646 | var join = searchMeta.getJoin(joinClause[0]), | |
647 | joinEntity = searchMeta.getEntity(join.entity), | |
648 | bridgeEntity = _.isString(joinClause[2]) ? searchMeta.getEntity(joinClause[2]) : null; | |
9446fbaa | 649 | _.each(_.cloneDeep(joinEntity.links), function(link) { |
957358aa CW |
650 | link.join = join.alias; |
651 | addTitle(link, join.label); | |
652 | links.push(link); | |
653 | }); | |
9446fbaa | 654 | _.each(_.cloneDeep(bridgeEntity && bridgeEntity.links), function(link) { |
957358aa CW |
655 | link.join = join.alias; |
656 | addTitle(link, join.label + (bridgeEntity.bridge_title ? ' ' + bridgeEntity.bridge_title : '')); | |
657 | links.push(link); | |
658 | }); | |
659 | }); | |
660 | // Links to implicit joins | |
661 | _.each(ctrl.savedSearch.api_params.select, function(fieldName) { | |
662 | if (!_.includes(fieldName, ' AS ')) { | |
7891ca94 | 663 | var info = searchMeta.parseExpr(fieldName).args[0]; |
7d527c18 | 664 | if (info.field && !info.suffix && !info.fn && info.field.type === 'Field' && (info.field.fk_entity || info.field.name !== info.field.fieldName)) { |
957358aa | 665 | var idFieldName = info.field.fk_entity ? fieldName : fieldName.substr(0, fieldName.lastIndexOf('.')), |
7891ca94 | 666 | idField = searchMeta.parseExpr(idFieldName).args[0].field; |
957358aa CW |
667 | if (!ctrl.canAggregate(idFieldName)) { |
668 | var joinEntity = searchMeta.getEntity(idField.fk_entity), | |
669 | label = (idField.join ? idField.join.label + ': ' : '') + (idField.input_attrs && idField.input_attrs.label || idField.label); | |
9446fbaa | 670 | _.each(_.cloneDeep(joinEntity && joinEntity.links), function(link) { |
957358aa CW |
671 | link.join = idFieldName; |
672 | addTitle(link, label); | |
673 | links.push(link); | |
674 | }); | |
675 | } | |
676 | } | |
677 | } | |
678 | }); | |
9446fbaa | 679 | return links; |
957358aa CW |
680 | }; |
681 | ||
02c7fc51 | 682 | function loadAfforms() { |
5c952e51 | 683 | ctrl.afforms = null; |
02c7fc51 CW |
684 | if (ctrl.afformEnabled && ctrl.savedSearch.id) { |
685 | var findDisplays = _.transform(ctrl.savedSearch.displays, function(findDisplays, display) { | |
686 | if (display.id && display.name) { | |
687 | findDisplays.push(['search_displays', 'CONTAINS', ctrl.savedSearch.name + '.' + display.name]); | |
688 | } | |
ea04af0c CW |
689 | }, [['search_displays', 'CONTAINS', ctrl.savedSearch.name]]); |
690 | afformLoad = crmApi4('Afform', 'get', { | |
691 | select: ['name', 'title', 'search_displays'], | |
692 | where: [['OR', findDisplays]] | |
693 | }).then(function(afforms) { | |
5c952e51 | 694 | ctrl.afforms = []; |
ea04af0c | 695 | _.each(afforms, function(afform) { |
5c952e51 CW |
696 | ctrl.afforms.push({ |
697 | title: afform.title, | |
698 | displays: afform.search_displays, | |
699 | link: ctrl.afformAdminEnabled ? CRM.url('civicrm/admin/afform#/edit/' + afform.name) : '', | |
02c7fc51 CW |
700 | }); |
701 | }); | |
5c952e51 | 702 | ctrl.afformCount = ctrl.afforms.length; |
ea04af0c | 703 | }); |
02c7fc51 CW |
704 | } |
705 | } | |
706 | ||
5c952e51 | 707 | // Creating an Afform opens a new tab, so when switching back after > 10 sec, re-check for Afforms |
02c7fc51 CW |
708 | $(window).on('focus', _.debounce(function() { |
709 | $scope.$apply(loadAfforms); | |
710 | }, 10000, {leading: true, trailing: false})); | |
711 | ||
25523059 CW |
712 | } |
713 | }); | |
714 | ||
715 | })(angular, CRM.$, CRM._); |