1 (function(angular
, $, _
) {
4 // Shared between router and searchMeta service
9 // Declare module and route/controller/services
10 angular
.module('crmSearchAdmin', CRM
.angRequires('crmSearchAdmin'))
12 .config(function($routeProvider
) {
13 $routeProvider
.when('/list', {
14 controller
: 'searchList',
15 templateUrl
: '~/crmSearchAdmin/searchList.html',
17 // Load data for lists
18 savedSearches: function(crmApi4
) {
19 return crmApi4('SavedSearch', 'get', {
26 'GROUP_CONCAT(display.name ORDER BY display.id) AS display_name',
27 'GROUP_CONCAT(display.label ORDER BY display.id) AS display_label',
28 'GROUP_CONCAT(display.type:icon ORDER BY display.id) AS display_icon',
29 'GROUP_CONCAT(DISTINCT group.title) AS groups'
31 join
: [['SearchDisplay AS display'], ['Group AS group']],
32 where
: [['api_entity', 'IS NOT NULL']],
38 $routeProvider
.when('/create/:entity', {
39 controller
: 'searchCreate',
40 reloadOnSearch
: false,
41 template
: '<crm-search-admin saved-search="$ctrl.savedSearch"></crm-search-admin>',
43 $routeProvider
.when('/edit/:id', {
44 controller
: 'searchEdit',
45 template
: '<crm-search-admin saved-search="$ctrl.savedSearch"></crm-search-admin>',
48 savedSearch: function($route
, crmApi4
) {
49 var params
= $route
.current
.params
;
50 return crmApi4('SavedSearch', 'get', {
51 where
: [['id', '=', params
.id
]],
53 groups
: ['Group', 'get', {select
: ['id', 'title', 'description', 'visibility', 'group_type'], where
: [['saved_search_id', '=', '$id']]}],
54 displays
: ['SearchDisplay', 'get', {where
: [['saved_search_id', '=', '$id']]}]
62 // Controller for creating a new search
63 .controller('searchCreate', function($scope
, $routeParams
, $location
) {
64 searchEntity
= $routeParams
.entity
;
67 api_entity
: searchEntity
,
69 // Changing entity will refresh the angular page
70 $scope
.$watch('$ctrl.savedSearch.api_entity', function(newEntity
, oldEntity
) {
71 if (newEntity
&& oldEntity
&& newEntity
!== oldEntity
) {
72 $location
.url('/create/' + newEntity
);
77 // Controller for editing a SavedSearch
78 .controller('searchEdit', function($scope
, savedSearch
) {
79 searchEntity
= savedSearch
.api_entity
;
80 this.savedSearch
= savedSearch
;
84 .factory('searchMeta', function() {
85 function getEntity(entityName
) {
87 return _
.find(CRM
.crmSearchAdmin
.schema
, {name
: entityName
});
90 // Get join metadata matching a given expression like "Email AS Contact_Email_contact_id_01"
91 function getJoin(fullNameOrAlias
) {
92 var alias
= _
.last(fullNameOrAlias
.split(' AS ')),
94 baseEntity
= searchEntity
,
100 join
= _
.find(CRM
.crmSearchAdmin
.joins
[baseEntity
], function(join
) {
101 return new RegExp('^' + join
.alias
+ '_\\d\\d').test(path
);
104 console
.warn( 'Join ' + fullNameOrAlias
+ ' not found.');
107 path
= path
.replace(join
.alias
+ '_', '');
108 var num
= parseInt(path
.substr(0, 2), 10);
109 baseEntity
= join
.entity
;
110 label
.push(join
.label
+ (num
> 1 ? ' ' + num
: ''));
111 path
= path
.replace(/^\d\d_?/, '');
113 result
= _
.assign(_
.cloneDeep(join
), {label
: label
.join(' - '), alias
: alias
});
114 // Add the numbered suffix to the join conditions
115 // If this is a deep join, also add the base entity prefix
116 var prefix
= alias
.replace(new RegExp('_?' + join
.alias
+ '_?\\d?\\d?$'), '');
117 _
.each(result
.conditions
, function(condition
) {
118 if (_
.isArray(condition
)) {
119 _
.each(condition
, function(ref
, side
) {
120 if (side
!== 1 && _
.includes(ref
, '.')) {
121 condition
[side
] = ref
.replace(join
.alias
+ '.', alias
+ '.');
122 } else if (side
!== 1 && prefix
.length
&& !_
.includes(ref
, '"') && !_
.includes(ref
, "'")) {
123 condition
[side
] = prefix
+ '.' + ref
;
130 function getField(fieldName
, entityName
) {
131 var dotSplit
= fieldName
.split('.'),
132 joinEntity
= dotSplit
.length
> 1 ? dotSplit
[0] : null,
133 name
= _
.last(dotSplit
).split(':')[0],
136 // Custom fields contain a dot in their fieldname
137 // If 3 segments, the first is the joinEntity and the last 2 are the custom field
138 if (dotSplit
.length
=== 3) {
139 name
= dotSplit
[1] + '.' + name
;
141 // If 2 segments, it's ambiguous whether this is a custom field or joined field. Search the main entity first.
142 if (dotSplit
.length
=== 2) {
143 field
= _
.find(getEntity(entityName
).fields
, {name
: dotSplit
[0] + '.' + name
});
145 field
.entity
= entityName
;
150 join
= getJoin(joinEntity
);
151 entityName
= getJoin(joinEntity
).entity
;
153 field
= _
.find(getEntity(entityName
).fields
, {name
: name
});
154 if (!field
&& join
&& join
.bridge
) {
155 field
= _
.find(getEntity(join
.bridge
).fields
, {name
: name
});
158 field
.entity
= entityName
;
162 function parseExpr(expr
) {
163 var result
= {fn
: null, modifier
: ''},
165 bracketPos
= expr
.indexOf('(');
166 if (bracketPos
>= 0) {
167 var parsed
= expr
.substr(bracketPos
).match(/[ ]?([A-Z]+[ ]+)?([\w.:]+)/);
168 fieldName
= parsed
[2];
169 result
.fn
= _
.find(CRM
.crmSearchAdmin
.functions
, {name
: expr
.substring(0, bracketPos
)});
170 result
.modifier
= _
.trim(parsed
[1]);
172 result
.field
= expr
? getField(fieldName
, searchEntity
) : undefined;
174 var split
= fieldName
.split(':'),
175 prefixPos
= split
[0].lastIndexOf(result
.field
.name
);
176 result
.path
= split
[0];
177 result
.prefix
= prefixPos
> 0 ? result
.path
.substring(0, prefixPos
) : '';
178 result
.suffix
= !split
[1] ? '' : ':' + split
[1];
183 getEntity
: getEntity
,
186 parseExpr
: parseExpr
,
187 getDefaultLabel: function(col
) {
188 var info
= parseExpr(col
),
189 label
= info
.field
.label
;
191 label
= '(' + info
.fn
.title
+ ') ' + label
;
195 // Find all possible search columns that could serve as contact_id for a smart group
196 getSmartGroupColumns: function(api_entity
, api_params
) {
197 var joins
= _
.pluck((api_params
.join
|| []), 0),
199 return _
.transform([api_entity
].concat(joins
), function(columns
, joinExpr
) {
200 var joinName
= joinExpr
.split(' AS '),
201 entityName
= joinName
[0],
202 entity
= getEntity(entityName
),
203 prefix
= joinName
[1] ? joinName
[1] + '.' : '';
204 _
.each(entity
.fields
, function(field
) {
205 if ((entityName
=== 'Contact' && field
.name
=== 'id') || field
.fk_entity
=== 'Contact') {
207 id
: prefix
+ field
.name
,
208 text
: entity
.title_plural
+ (entityCount
[entityName
] ? ' ' + entityCount
[entityName
] : '') + ': ' + field
.label
,
213 entityCount
[entityName
] = 1 + (entityCount
[entityName
] || 1);
219 })(angular
, CRM
.$, CRM
._
);