*
* Generated from xml/schema/CRM/Contact/SavedSearch.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:5e5799b7755c435363f2c8cdafd13055)
+ * (GenCodeChecksum:bca384578ea59c0cf4f0a80617c788fe)
*/
/**
'localizable' => 0,
'html' => [
'type' => 'Text',
+ 'label' => ts("Label"),
],
'add' => '5.32',
],
* Stores search parameters for populating smart groups with live results.
*
* @see https://docs.civicrm.org/user/en/latest/organising-your-data/smart-groups/
- * @searchable none
+ * @searchable secondary
* @since 5.24
* @package Civi\Api4
*/
.config(function($routeProvider) {
$routeProvider.when('/list', {
- controller: 'searchList',
- templateUrl: '~/crmSearchAdmin/searchList.html',
- resolve: {
- // Load data for lists
- savedSearches: function(crmApi4) {
- return crmApi4('SavedSearch', 'get', {
- select: [
- 'id',
- 'name',
- 'label',
- 'api_entity',
- 'api_params',
- 'created_id.display_name',
- 'modified_id.display_name',
- 'created_date',
- 'modified_date',
- 'GROUP_CONCAT(display.name ORDER BY display.id) AS display_name',
- 'GROUP_CONCAT(display.label ORDER BY display.id) AS display_label',
- 'GROUP_CONCAT(display.type:icon ORDER BY display.id) AS display_icon',
- 'GROUP_CONCAT(display.acl_bypass ORDER BY display.id) AS display_acl_bypass',
- 'GROUP_CONCAT(DISTINCT group.title) AS groups'
- ],
- join: [['SearchDisplay AS display'], ['Group AS group']],
- where: [['api_entity', 'IS NOT NULL']],
- groupBy: ['id']
- });
- }
- }
+ controller: function() {
+ searchEntity = 'SavedSearch';
+ },
+ template: '<crm-search-admin-search-listing></crm-search-admin-search-listing>',
});
$routeProvider.when('/create/:entity', {
controller: 'searchCreate',
key: info.alias,
dataType: (info.fn && info.fn.dataType) || (info.field && info.field.data_type)
}, defaults);
- if (defaults.label) {
+ if (defaults.label === true) {
values.label = getDefaultLabel(fieldExpr);
}
return values;
+++ /dev/null
-(function(angular, $, _) {
- "use strict";
-
- angular.module('crmSearchAdmin').controller('searchList', function($scope, savedSearches, crmApi4, searchMeta) {
- var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
- ctrl = $scope.$ctrl = this;
- $scope.formatDate = CRM.utils.formatDate;
- this.savedSearches = savedSearches;
- this.sortField = 'modified_date';
- this.sortDir = true;
- this.afformEnabled = CRM.crmSearchAdmin.afformEnabled;
- this.afformAdminEnabled = CRM.crmSearchAdmin.afformAdminEnabled;
-
- _.each(savedSearches, function(search) {
- search.entity_title = searchMeta.getEntity(search.api_entity).title_plural;
- search.permissionToEdit = CRM.checkPerm('all CiviCRM permissions and ACLs') || !_.includes(search.display_acl_bypass, true);
- search.afform_count = 0;
- });
-
- this.searchPath = CRM.url('civicrm/search');
- this.afformPath = CRM.url('civicrm/admin/afform');
-
- this.encode = function(params) {
- return encodeURI(angular.toJson(params));
- };
-
- // Change sort field/direction when clicking a column header
- this.sortBy = function(col) {
- ctrl.sortDir = ctrl.sortField === col ? !ctrl.sortDir : false;
- ctrl.sortField = col;
- };
-
- this.deleteSearch = function(search) {
- var index = _.findIndex(savedSearches, {id: search.id});
- if (index > -1) {
- crmApi4([
- ['Group', 'delete', {where: [['saved_search_id', '=', search.id]]}],
- ['SavedSearch', 'delete', {where: [['id', '=', search.id]]}]
- ]);
- savedSearches.splice(index, 1);
- }
- };
-
- this.loadAfforms = function() {
- if (ctrl.afforms || ctrl.afforms === null) {
- return;
- }
- ctrl.afforms = null;
- crmApi4('Afform', 'get', {
- select: ['layout', 'name', 'title', 'server_route'],
- where: [['type', '=', 'search']],
- layoutFormat: 'html'
- }).then(function(afforms) {
- ctrl.afforms = {};
- _.each(afforms, function(afform) {
- var searchName = afform.layout.match(/<crm-search-display-[^>]+search-name[ ]*=[ ]*['"]([^"']+)/);
- if (searchName) {
- var search = _.find(ctrl.savedSearches, {name: searchName[1]});
- if (search) {
- search.afform_count++;
- ctrl.afforms[searchName[1]] = ctrl.afforms[searchName[1]] || [];
- ctrl.afforms[searchName[1]].push({
- title: afform.title,
- name: afform.name,
- // FIXME: This is the view url, currently not exposed to the UI, as BS3 doesn't support submenus.
- url: afform.server_route ? CRM.url(afform.server_route) : null
- });
- }
- }
- });
- });
- };
-
- });
-
-})(angular, CRM.$, CRM._);
+++ /dev/null
-<div id="bootstrap-theme" class="crm-search crm-search-admin-list">
- <h1 crm-page-title>{{:: ts('Saved Searches') }}</h1>
- <div class="form-inline">
- <label for="search-list-filter">{{:: ts('Filter') }}</label>
- <input class="form-control" type="search" id="search-list-filter" ng-model="$ctrl.searchFilter" placeholder="">
- <a class="btn btn-primary pull-right" href="#/create/Contact/">
- <i class="crm-i fa-plus"></i>
- {{:: ts('New Search') }}
- </a>
- </div>
- <table>
- <thead>
- <tr>
- <th title="{{:: ts('Click to sort') }}" ng-click="$ctrl.sortBy('label')">
- <i class="crm-i fa-sort disabled" ng-if="$ctrl.sortField !== 'label'"></i>
- <i class="crm-i fa-sort-{{ $ctrl.sortDir ? 'asc' : 'desc' }}" ng-if="$ctrl.sortField === 'label'"></i>
- {{:: ts('Label') }}
- </th>
- <th title="{{:: ts('Click to sort') }}" ng-click="$ctrl.sortBy('entity_title')">
- <i class="crm-i fa-sort disabled" ng-if="$ctrl.sortField !== 'entity_title'"></i>
- <i class="crm-i fa-sort-{{ $ctrl.sortDir ? 'asc' : 'desc' }}" ng-if="$ctrl.sortField === 'entity_title'"></i>
- {{:: ts('For') }}
- </th>
- <th title="{{:: ts('Click to sort') }}" ng-click="$ctrl.sortBy('display_name.length')">
- <i class="crm-i fa-sort disabled" ng-if="$ctrl.sortField !== 'display_name.length'"></i>
- <i class="crm-i fa-sort-{{ $ctrl.sortDir ? 'asc' : 'desc' }}" ng-if="$ctrl.sortField === 'display_name.length'"></i>
- {{:: ts('Displays') }}
- </th>
- <th title="{{:: ts('Click to sort') }}" ng-click="$ctrl.sortBy('groups[0]')">
- <i class="crm-i fa-sort disabled" ng-if="$ctrl.sortField !== 'groups[0]'"></i>
- <i class="crm-i fa-sort-{{ $ctrl.sortDir ? 'asc' : 'desc' }}" ng-if="$ctrl.sortField === 'groups[0]'"></i>
- {{:: ts('Smart Group') }}
- </th>
- <th ng-if="$ctrl.afformEnabled" ng-click="$ctrl.sortBy('afform_count')">
- <i class="crm-i fa-sort disabled" ng-if="$ctrl.sortField !== 'afform_count'"></i>
- <i class="crm-i fa-sort-{{ $ctrl.sortDir ? 'asc' : 'desc' }}" ng-if="$ctrl.sortField === 'afform_count'"></i>
- {{:: ts('Forms') }}
- </th>
- <th title="{{:: ts('Click to sort') }}" ng-click="$ctrl.sortBy('created_date')">
- <i class="crm-i fa-sort disabled" ng-if="$ctrl.sortField !== 'created_date'"></i>
- <i class="crm-i fa-sort-{{ $ctrl.sortDir ? 'asc' : 'desc' }}" ng-if="$ctrl.sortField === 'created_date'"></i>
- {{:: ts('Created') }}
- </th>
- <th title="{{:: ts('Click to sort') }}" ng-click="$ctrl.sortBy('modified_date')">
- <i class="crm-i fa-sort disabled" ng-if="$ctrl.sortField !== 'modified_date'"></i>
- <i class="crm-i fa-sort-{{ $ctrl.sortDir ? 'asc' : 'desc' }}" ng-if="$ctrl.sortField === 'modified_date'"></i>
- {{:: ts('Last Modified') }}
- </th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="search in $ctrl.savedSearches | filter:$ctrl.searchFilter | orderBy:$ctrl.sortField:$ctrl.sortDir">
- <td>{{:: search.label }}</td>
- <td>{{:: search.entity_title }}</td>
- <td>
- <div class="btn-group">
- <button type="button" disabled ng-if="!search.display_name" class="btn btn-xs dropdown-toggle btn-primary-outline">
- {{:: ts('0 Displays') }}
- </button>
- <button type="button" ng-if="search.display_name" class="btn btn-xs dropdown-toggle btn-primary-outline" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
- {{:: search.display_name.length === 1 ? ts('1 Display') : ts('%1 Displays', {1: search.display_name.length}) }} <span class="caret"></span>
- </button>
- <ul class="dropdown-menu" ng-if=":: search.display_name.length">
- <li ng-repeat="display_name in search.display_name" ng-class="{disabled: search.display_acl_bypass[$index]}" title="{{:: search.display_acl_bypass[$index] ? ts('Display has permissions disabled') : ts('View display') }}">
- <a ng-href="{{:: search.display_acl_bypass[$index] ? '' : $ctrl.searchPath + '#/display/' + search.name + '/' + display_name }}" target="_blank">
- <i class="fa {{:: search.display_icon[$index] }}"></i>
- {{:: search.display_label[$index] }}
- </a>
- </li>
- </ul>
- </div>
- </td>
- <td>{{:: search.groups.join(', ') }}</td>
- <td ng-if="::$ctrl.afformEnabled">
- <div class="btn-group">
- <button type="button" ng-click="$ctrl.loadAfforms()" ng-if="search.display_name" class="btn btn-xs dropdown-toggle btn-primary-outline" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
- {{ $ctrl.afforms ? (search.afform_count === 1 ? ts('1 Form') : ts('%1 Forms', {1: search.afform_count})) : ts('Forms...') }}
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu">
- <li ng-repeat="display_name in search.display_name" ng-if="::$ctrl.afformAdminEnabled">
- <a href="{{:: $ctrl.afformPath + '#/create/search/' + search.name + '.' + display_name }}">
- <i class="fa fa-plus"></i> {{:: ts('Create form for %1', {1: search.display_label[$index]}) }}
- </a>
- </li>
- <li class="divider" role="separator" ng-if="::$ctrl.afformAdminEnabled"></li>
- <li ng-if="!search.afform_count" class="disabled">
- <a href>
- <i ng-if="!$ctrl.afforms" class="crm-i fa-spinner fa-spin"></i>
- <em ng-if="$ctrl.afforms && !$ctrl.afforms[search.name]">{{:: ts('None Found') }}</em>
- </a>
- </li>
- <li ng-if="$ctrl.afforms" ng-repeat="afform in $ctrl.afforms[search.name]" title="{{:: ts('Edit form') }}">
- <a href="{{:: $ctrl.afformPath + '#/edit/' + afform.name }}">
- <i class="crm-i fa-pencil-square-o"></i>
- {{:: afform.title }}
- </a>
- </li>
- </ul>
- </div>
- </td>
- <td title="{{:: formatDate(search.created_date, null, true) }}">
- {{:: search['created_id.display_name'] ? ts('%1 by %2', {1: formatDate(search.created_date), 2: search['created_id.display_name']}) : formatDate(search.created_date) }}
- </td>
- <td title="{{:: formatDate(search.modified_date, null, true) }}">
- {{:: search['modified_id.display_name'] ? ts('%1 by %2', {1: formatDate(search.modified_date), 2: search['modified_id.display_name']}) : formatDate(search.modified_date) }}
- </td>
- <td class="text-right">
- <a class="btn btn-xs btn-default" href="#/edit/{{:: search.id }}" ng-if="search.permissionToEdit">{{:: ts('Edit') }}</a>
- <a class="btn btn-xs btn-default" href="#/create/{{:: search.api_entity + '?params=' + $ctrl.encode(search.api_params) }}">{{:: ts('Clone') }}</a>
- <a href class="btn btn-xs btn-danger" crm-confirm="{type: 'delete', obj: search}" on-yes="$ctrl.deleteSearch(search)">{{:: ts('Delete') }}</a>
- </td>
- </tr>
- <tr ng-if="$ctrl.savedSearches.length === 0">
- <td colspan="9">
- <p class="messages status no-popup text-center">
- {{:: ts('No saved searches.')}}
- </p>
- </td>
- </tr>
- </tbody>
- </table>
-</div>
--- /dev/null
+<div class="btn-group" ng-if=":: row.display_name.raw">
+ <button type="button" ng-click="$ctrl.loadAfforms(); row.openAfformMenu = true;" class="btn btn-xs dropdown-toggle btn-primary-outline" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{ $ctrl.afforms ? (row.afform_count === 1 ? ts('1 Form') : ts('%1 Forms', {1: row.afform_count})) : ts('Forms...') }}
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" ng-if=":: row.openAfformMenu">
+ <li ng-repeat="display_name in row.display_name.raw" ng-if="::$ctrl.afformAdminEnabled">
+ <a target="_blank" href="{{:: $ctrl.afformPath + '#/create/search/' + row.name.raw + '.' + display_name }}">
+ <i class="fa fa-plus"></i> {{:: ts('Create form for %1', {1: row.display_label.raw[$index]}) }}
+ </a>
+ </li>
+ <li class="divider" role="separator" ng-if="::$ctrl.afformAdminEnabled"></li>
+ <li ng-if="!row.afform_count" class="disabled">
+ <a href>
+ <i ng-if="!$ctrl.afforms" class="crm-i fa-spinner fa-spin"></i>
+ <em ng-if="$ctrl.afforms && !$ctrl.afforms[row.name.raw]">{{:: ts('None Found') }}</em>
+ </a>
+ </li>
+ <li ng-if="$ctrl.afforms" ng-repeat="afform in $ctrl.afforms[row.name.raw]" title="{{:: ts('Edit form') }}">
+ <a target="_blank" href="{{:: $ctrl.afformPath + '#/edit/' + afform.name }}">
+ <i class="crm-i fa-pencil-square-o"></i>
+ {{:: afform.title }}
+ </a>
+ </li>
+ </ul>
+</div>
--- /dev/null
+<a class="btn btn-xs btn-default" href="#/edit/{{:: row.id.raw }}" ng-if="row.permissionToEdit">
+ {{:: ts('Edit') }}
+</a>
+<a class="btn btn-xs btn-default" href="#/create/{{:: row.api_entity.raw + '?params=' + $ctrl.encode(row.api_params.raw) }}">
+ {{:: ts('Clone') }}
+</a>
+<a href class="btn btn-xs btn-danger" crm-confirm="{type: 'delete', obj: row}" on-yes="$ctrl.deleteSearch(row)">
+ {{:: ts('Delete') }}
+</a>
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ // Specialized searchDisplay, only used by Admins
+ angular.module('crmSearchAdmin').component('crmSearchAdminSearchListing', {
+ templateUrl: '~/crmSearchAdmin/searchListing/crmSearchAdminSearchListing.html',
+ controller: function($scope, crmApi4, crmStatus, searchMeta, searchDisplayBaseTrait, searchDisplaySortableTrait) {
+ var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
+ // Mix in traits to this controller
+ ctrl = angular.extend(this, searchDisplayBaseTrait, searchDisplaySortableTrait);
+
+ this.searchDisplayPath = CRM.url('civicrm/search');
+ this.afformPath = CRM.url('civicrm/admin/afform');
+ this.afformEnabled = CRM.crmSearchAdmin.afformEnabled;
+ this.afformAdminEnabled = CRM.crmSearchAdmin.afformAdminEnabled;
+
+ this.apiEntity = 'SavedSearch';
+ this.search = {
+ api_entity: 'SavedSearch',
+ api_params: {
+ version: 4,
+ select: [
+ 'id',
+ 'name',
+ 'label',
+ 'api_entity',
+ 'api_entity:label',
+ 'api_params',
+ 'created_date',
+ 'modified_date',
+ 'GROUP_CONCAT(display.name ORDER BY display.id) AS display_name',
+ 'GROUP_CONCAT(display.label ORDER BY display.id) AS display_label',
+ 'GROUP_CONCAT(display.type:icon ORDER BY display.id) AS display_icon',
+ 'GROUP_CONCAT(display.acl_bypass ORDER BY display.id) AS display_acl_bypass',
+ 'GROUP_CONCAT(DISTINCT group.title) AS groups'
+ ],
+ join: [['SearchDisplay AS display'], ['Group AS group']],
+ where: [['api_entity', 'IS NOT NULL']],
+ groupBy: ['id']
+ }
+ };
+
+ this.$onInit = function() {
+ buildDisplaySettings();
+ this.initializeDisplay($scope, $());
+ };
+
+ this.onPostRun.push(function(result) {
+ _.each(result, function(row) {
+ row.permissionToEdit = CRM.checkPerm('all CiviCRM permissions and ACLs') || !_.includes(row.display_acl_bypass.raw, true);
+ // Saves rendering cycles to not show an empty menu of search displays
+ if (!row.display_name.raw) {
+ row.openDisplayMenu = false;
+ }
+ });
+ updateAfformCounts();
+ });
+
+ this.encode = function(params) {
+ return encodeURI(angular.toJson(params));
+ };
+
+ this.deleteSearch = function(search) {
+ crmStatus({start: ts('Deleting...'), success: ts('Search Deleted')},
+ crmApi4('SavedSearch', 'delete', {where: [['id', '=', search.id.raw]]}).then(function() {
+ ctrl.rowCount = null;
+ ctrl.runSearch();
+ })
+ );
+ };
+
+ function buildDisplaySettings() {
+ ctrl.display = {
+ type: 'table',
+ settings: {
+ limit: CRM.crmSearchAdmin.defaultPagerSize,
+ pager: {show_count: true, expose_limit: true},
+ actions: false,
+ sort: [['modified_date', 'DESC']],
+ columns: [
+ searchMeta.fieldToColumn('label', {
+ label: true,
+ title: ts('Edit Label'),
+ editable: {entity: 'SavedSearch', id: 'id', name: 'label', value: 'label'}
+ }),
+ searchMeta.fieldToColumn('api_entity:label', {
+ label: ts('For'),
+ }),
+ {
+ type: 'include',
+ label: ts('Displays'),
+ path: '~/crmSearchAdmin/searchListing/displays.html'
+ },
+ searchMeta.fieldToColumn('GROUP_CONCAT(DISTINCT group.title) AS groups', {
+ label: ts('Smart Group')
+ }),
+ searchMeta.fieldToColumn('created_date', {
+ label: ts('Created'),
+ dataType: 'Date',
+ rewrite: ts('%1 by %2', {1: '[created_date]', 2: '[created_id.display_name]'})
+ }),
+ searchMeta.fieldToColumn('modified_date', {
+ label: ts('Last Modified'),
+ dataType: 'Date',
+ rewrite: ts('%1 by %2', {1: '[modified_date]', 2: '[modified_id.display_name]'})
+ }),
+ {
+ type: 'include',
+ alignment: 'text-right',
+ path: '~/crmSearchAdmin/searchListing/buttons.html'
+ }
+ ]
+ }
+ };
+ if (ctrl.afformEnabled) {
+ ctrl.display.settings.columns.splice(3, 0, {
+ type: 'include',
+ label: ts('Forms'),
+ path: '~/crmSearchAdmin/searchListing/afforms.html'
+ });
+ }
+ ctrl.settings = ctrl.display.settings;
+ }
+
+ this.loadAfforms = function() {
+ if (ctrl.afforms || ctrl.afforms === null) {
+ return;
+ }
+ ctrl.afforms = null;
+ crmApi4('Afform', 'get', {
+ select: ['layout', 'name', 'title', 'server_route'],
+ where: [['type', '=', 'search']],
+ layoutFormat: 'html'
+ }).then(function(afforms) {
+ ctrl.afforms = {};
+ _.each(afforms, function(afform) {
+ var searchName = afform.layout.match(/<crm-search-display-[^>]+search-name[ ]*=[ ]*['"]([^"']+)/);
+ if (searchName) {
+ ctrl.afforms[searchName[1]] = ctrl.afforms[searchName[1]] || [];
+ ctrl.afforms[searchName[1]].push({
+ title: afform.title,
+ name: afform.name,
+ // FIXME: This is the view url, currently not exposed to the UI, as BS3 doesn't support submenus.
+ url: afform.server_route ? CRM.url(afform.server_route) : null
+ });
+ }
+ });
+ updateAfformCounts();
+ });
+ };
+
+ function updateAfformCounts() {
+ _.each(ctrl.results, function(row) {
+ row.afform_count = ctrl.afforms && ctrl.afforms[row.name.raw] && ctrl.afforms[row.name.raw].length || 0;
+ });
+ }
+
+ }
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+<div id="bootstrap-theme" class="crm-search">
+ <h1 crm-page-title>{{:: ts('Saved Searches') }}</h1>
+ <div class="form-inline">
+ <label for="search-list-filter">{{:: ts('Filter') }}</label>
+ <input class="form-control" type="search" id="search-list-filter" ng-model="$ctrl.filters.label" placeholder="">
+ <a class="btn btn-primary pull-right" href="#/create/Contact/">
+ <i class="crm-i fa-plus"></i>
+ {{:: ts('New Search') }}
+ </a>
+ </div>
+ <div ng-include="'~/crmSearchDisplayTable/crmSearchDisplayTable.html'"></div>
+</div>
--- /dev/null
+<div class="btn-group">
+ <button type="button" disabled ng-if="!row.display_name.raw" class="btn btn-xs dropdown-toggle btn-primary-outline">
+ {{:: ts('0 Displays') }}
+ </button>
+ <button type="button" ng-if=":: row.display_name.raw" ng-click="row.openDisplayMenu = true" class="btn btn-xs dropdown-toggle btn-primary-outline" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{:: row.display_name.raw.length === 1 ? ts('1 Display') : ts('%1 Displays', {1: row.display_name.raw.length}) }} <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" ng-if=":: row.openDisplayMenu">
+ <li ng-repeat="display_name in row.display_name.raw" ng-class="{disabled: row.display_acl_bypass.raw[$index]}" title="{{:: row.display_acl_bypass.raw[$index] ? ts('Display has permissions disabled') : ts('View display') }}">
+ <a ng-href="{{:: row.display_acl_bypass.raw[$index] ? '' : $ctrl.searchDisplayPath + '#/display/' + row.name.raw + '/' + display_name }}" target="_blank">
+ <i class="fa {{:: row.display_icon.rw[$index] }}"></i>
+ {{:: row.display_label.raw[$index] }}
+ </a>
+ </li>
+ </ul>
+</div>
-#bootstrap-theme.crm-search-admin-list th[ng-click] {
- cursor: pointer;
-}
-#bootstrap-theme.crm-search-admin-list th i.fa-sort-desc,
-#bootstrap-theme.crm-search-admin-list th i.fa-sort-asc {
- color: #1a5a82;
-}
-#bootstrap-theme.crm-search-admin-list th:not(:hover) i.fa-sort {
- opacity: .5;
-}
#bootstrap-theme .crm-search-criteria-column {
min-width: 500px;
<default>NULL</default>
<comment>Administrative label for search</comment>
<html>
+ <label>Label</label>
<type>Text</type>
</html>
<add>5.32</add>