<?php
-
-class CRM_Search_Page_Ang extends CRM_Core_Page {
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Angular base page for search admin
+ */
+class CRM_Search_Page_Admin extends CRM_Core_Page {
/**
* @var string[]
*/
'schema' => $this->schema,
'links' => $this->getLinks(),
'loadOptions' => $this->loadOptions,
- 'actions' => $this->getActions(),
'functions' => CRM_Api4_Page_Api4Explorer::getSqlFunctions(),
];
Civi::resources()
- ->addPermissions(['edit groups', 'administer reserved groups'])
->addBundle('bootstrap3')
->addVars('search', $vars);
// Load angular module
$loader = new Civi\Angular\AngularLoader();
- $loader->setModules(['search']);
+ $loader->setModules(['searchAdmin']);
$loader->setPageName('civicrm/search');
$loader->useApp([
'defaultRoute' => '/create/Contact',
return array_filter($results);
}
- /**
- * @return array[]
- */
- private function getActions() {
- // Note: the placeholder %1 will be replaced with entity name on the clientside
- $actions = [
- 'export' => [
- 'title' => ts('Export %1'),
- 'icon' => 'fa-file-excel-o',
- 'entities' => array_keys(CRM_Export_BAO_Export::getComponents()),
- 'crmPopup' => [
- 'path' => "'civicrm/export/standalone'",
- 'query' => "{entity: entity, id: ids.join(',')}",
- ],
- ],
- 'update' => [
- 'title' => ts('Update %1'),
- 'icon' => 'fa-save',
- 'entities' => [],
- 'uiDialog' => ['templateUrl' => '~/search/crmSearchActions/crmSearchActionUpdate.html'],
- ],
- 'delete' => [
- 'title' => ts('Delete %1'),
- 'icon' => 'fa-trash',
- 'entities' => [],
- 'uiDialog' => ['templateUrl' => '~/search/crmSearchActions/crmSearchActionDelete.html'],
- ],
- ];
-
- // Check permissions for update & delete actions
- foreach ($this->allowedEntities as $entity) {
- $result = civicrm_api4($entity, 'getActions', [
- 'where' => [['name', 'IN', ['update', 'delete']]],
- ], ['name']);
- foreach ($result as $action) {
- // Contacts have their own delete action
- if (!($entity === 'Contact' && $action === 'delete')) {
- $actions[$action]['entities'][] = $entity;
- }
- }
- }
-
- // Add contact tasks which support standalone mode (with a 'url' property)
- $contactTasks = CRM_Contact_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission());
- foreach (CRM_Contact_Task::tasks() as $id => $task) {
- if (isset($contactTasks[$id]) && !empty($task['url'])) {
- $actions['contact.' . $id] = [
- 'title' => $task['title'],
- 'entities' => ['Contact'],
- 'icon' => $task['icon'] ?? 'fa-gear',
- 'crmPopup' => [
- 'path' => "'{$task['url']}'",
- 'query' => "{cids: ids.join(',')}",
- ],
- ];
- }
- }
-
- return $actions;
- }
-
}
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Search;
+
+/**
+ * Class Tasks
+ * @package Civi\Search
+ */
+class Actions {
+
+ /**
+ * @return array
+ */
+ public static function getActionSettings():array {
+ return [
+ 'tasks' => self::getTasks(),
+ 'groupOptions' => self::getGroupOptions(),
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public static function getGroupOptions():array {
+ return \Civi\Api4\Group::getFields(FALSE)
+ ->setLoadOptions(['id', 'label'])
+ ->addWhere('name', 'IN', ['group_type', 'visibility'])
+ ->execute()
+ ->indexBy('name')
+ ->column('options');
+ }
+
+ /**
+ * @return array
+ */
+ public static function getTasks():array {
+ // Note: the placeholder %1 will be replaced with entity name on the clientside
+ $tasks = [
+ 'export' => [
+ 'title' => ts('Export %1'),
+ 'icon' => 'fa-file-excel-o',
+ 'entities' => array_keys(\CRM_Export_BAO_Export::getComponents()),
+ 'crmPopup' => [
+ 'path' => "'civicrm/export/standalone'",
+ 'query' => "{entity: entity, id: ids.join(',')}",
+ ],
+ ],
+ 'update' => [
+ 'title' => ts('Update %1'),
+ 'icon' => 'fa-save',
+ 'entities' => [],
+ 'uiDialog' => ['templateUrl' => '~/searchActions/crmSearchActionUpdate.html'],
+ ],
+ 'delete' => [
+ 'title' => ts('Delete %1'),
+ 'icon' => 'fa-trash',
+ 'entities' => [],
+ 'uiDialog' => ['templateUrl' => '~/searchActions/crmSearchActionDelete.html'],
+ ],
+ ];
+
+ // Add contact tasks which support standalone mode (with a 'url' property)
+ $contactTasks = \CRM_Contact_Task::permissionedTaskTitles(\CRM_Core_Permission::getPermission());
+ foreach (\CRM_Contact_Task::tasks() as $id => $task) {
+ if (isset($contactTasks[$id]) && !empty($task['url']) && $task['url'] !== 'civicrm/task/delete-contact') {
+ $tasks['contact.' . $id] = [
+ 'title' => $task['title'],
+ 'entities' => ['Contact'],
+ 'icon' => $task['icon'] ?? 'fa-gear',
+ 'crmPopup' => [
+ 'path' => "'{$task['url']}'",
+ 'query' => "{cids: ids.join(',')}",
+ ],
+ ];
+ }
+ }
+
+ return $tasks;
+ }
+
+}
--- /dev/null
+<?php
+// Autoloader data for search actions.
+return [
+ 'js' => [
+ 'ang/searchActions.module.js',
+ 'ang/searchActions/*.js',
+ 'ang/searchActions/*/*.js',
+ ],
+ 'partials' => [
+ 'ang/searchActions',
+ ],
+ 'basePages' => [],
+ 'requires' => ['crmUi', 'crmUtil', 'dialogService', 'api4'],
+ 'settingsFactory' => ['\Civi\Search\Actions', 'getActionSettings'],
+ 'permissions' => ['edit groups', 'administer reserved groups'],
+];
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ // Declare module
+ angular.module('searchActions', CRM.angRequires('searchActions'));
+
+})(angular, CRM.$, CRM._);
(function(angular, $, _) {
"use strict";
- angular.module('search').controller('SaveSmartGroup', function ($scope, $element, $timeout, crmApi4, dialogService, searchMeta) {
+ angular.module('searchActions').controller('SaveSmartGroup', function ($scope, $element, $timeout, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts(),
model = $scope.model,
joins = _.pluck((model.api_params.join || []), 0),
$scope.perm = {
administerReservedGroups: CRM.checkPerm('administer reserved groups')
};
- $scope.groupFields = _.indexBy(_.find(CRM.vars.search.schema, {name: 'Group'}).fields, 'name');
+ $scope.groupOptions = CRM.searchActions.groupOptions;
$element.on('change', '#api-save-search-select-group', function() {
if ($(this).val()) {
$scope.$apply(function() {
(function(angular, $, _) {
"use strict";
- angular.module('search').controller('crmSearchActionDelete', function($scope, crmApi4, dialogService, searchMeta) {
+ angular.module('searchActions').controller('crmSearchActionDelete', function($scope, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts(),
model = $scope.model,
ctrl = $scope.$ctrl = this;
(function(angular, $, _) {
"use strict";
- angular.module('search').controller('crmSearchActionUpdate', function ($scope, $timeout, crmApi4, dialogService, searchMeta) {
+ angular.module('searchActions').controller('crmSearchActionUpdate', function ($scope, $timeout, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts(),
model = $scope.model,
ctrl = $scope.$ctrl = this;
(function(angular, $, _) {
"use strict";
- angular.module('search').component('crmSearchActions', {
+ angular.module('searchActions').component('crmSearchActions', {
bindings: {
entity: '<',
refresh: '&',
ids: '<'
},
- templateUrl: '~/search/crmSearchActions.html',
+ templateUrl: '~/searchActions/crmSearchActions.html',
controller: function($scope, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts(),
- ctrl = this;
+ ctrl = this,
+ initialized = false,
+ unwatchIDs = $scope.$watch('$ctrl.ids.length', watchIDs);
- this.$onInit = function() {
+ function watchIDs() {
+ if (ctrl.ids && ctrl.ids.length && !initialized) {
+ unwatchIDs();
+ initialized = true;
+ initialize();
+ }
+ }
+
+ function initialize() {
var entityTitle = searchMeta.getEntity(ctrl.entity).titlePlural;
- if (!ctrl.actions) {
- var actions = _.transform(_.cloneDeep(CRM.vars.search.actions), function (actions, action) {
+ crmApi4(ctrl.entity, 'getActions', {
+ where: [['name', 'IN', ['update', 'delete']]],
+ }, ['name']).then(function(allowed) {
+ _.each(allowed, function(action) {
+ CRM.searchActions.tasks[action].entities.push(ctrl.entity);
+ });
+ var actions = _.transform(_.cloneDeep(CRM.searchActions.tasks), function(actions, action) {
if (_.includes(action.entities, ctrl.entity)) {
action.title = action.title.replace('%1', entityTitle);
actions.push(action);
}
}, []);
ctrl.actions = _.sortBy(actions, 'title');
- }
- };
+ });
+ }
this.isActionAllowed = function(action) {
return !action.number || $scope.eval('' + $ctrl.ids.length + action.number);
<div class="btn-group" title="{{:: ts('Perform action on selected items.') }}">
- <button type="button" ng-disabled="!$ctrl.ids.length" ng-click="$ctrl.init()" class="btn form-control dropdown-toggle btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <button type="button" ng-disabled="!$ctrl.ids.length" class="btn form-control dropdown-toggle btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{:: ts('Action') }} <span class="caret"></span>
</button>
<ul class="dropdown-menu" ng-if=":: $ctrl.actions">
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('searchActions').directive('saveSmartGroup', function() {
+ return {
+ bindToController: {
+ load: '<',
+ entity: '<',
+ params: '<'
+ },
+ controller: function ($scope, $element, dialogService) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = this;
+
+ $scope.saveGroup = function () {
+ var model = {
+ title: '',
+ description: '',
+ visibility: 'User and User Admin Only',
+ group_type: [],
+ id: ctrl.load ? ctrl.load.id : null,
+ api_entity: ctrl.entity,
+ api_params: _.cloneDeep(angular.extend({}, ctrl.params, {version: 4}))
+ };
+ delete model.api_params.orderBy;
+ if (ctrl.load && ctrl.load.api_params && ctrl.load.api_params.select && ctrl.load.api_params.select[0]) {
+ model.api_params.select.unshift(ctrl.load.api_params.select[0]);
+ }
+ var options = CRM.utils.adjustDialogDefaults({
+ autoOpen: false,
+ title: ts('Save smart group')
+ });
+ dialogService.open('saveSearchDialog', '~/searchActions/saveSmartGroup.html', model, options)
+ .then(function () {
+ if (ctrl.load) {
+ ctrl.load.saved = true;
+ }
+ });
+ };
+ }
+ };
+ });
+
+})(angular, CRM.$, CRM._);
<textarea class="form-control" ng-model="model.description"></textarea>
<div class="form-inline">
<label>{{:: ts('Group Type:') }} </label>
- <div class="checkbox" ng-repeat="option in groupFields.group_type.options track by option.id">
+ <div class="checkbox" ng-repeat="option in groupOptions.group_type track by option.id">
<label>
<input type="checkbox" checklist-model="model.group_type" checklist-value="option.id">
{{ option.label }}
</div>
<div class="form-inline">
<label>{{:: ts('Visibility:') }}</label>
- <select class="form-control" ng-model="model.visibility" ng-options="item.id as item.label for item in groupFields.visibility.options track by item.id" crm-ui-select></select>
+ <select class="form-control" ng-model="model.visibility" ng-options="item.id as item.label for item in groupOptions.visibility track by item.id" crm-ui-select></select>
</div>
<hr />
<div class="buttons pull-right">
// Autoloader data for search builder.
return [
'js' => [
- 'ang/*.js',
- 'ang/search/*.js',
- 'ang/search/*/*.js',
+ 'ang/searchAdmin.module.js',
+ 'ang/searchAdmin/*.js',
+ 'ang/searchAdmin/*/*.js',
],
'css' => [
'css/*.css',
],
'partials' => [
- 'ang/search',
+ 'ang/searchAdmin',
],
'basePages' => [],
- 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'crmRouteBinder', 'ui.sortable', 'ui.bootstrap', 'dialogService', 'api4'],
+ 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'crmRouteBinder', 'ui.sortable', 'ui.bootstrap', 'dialogService', 'api4', 'searchActions'],
];
undefined;
// Declare module and route/controller/services
- angular.module('search', CRM.angRequires('search'))
+ angular.module('searchAdmin', CRM.angRequires('searchAdmin'))
.config(function($routeProvider) {
$routeProvider.when('/:mode/:entity/:name?', {
(function(angular, $, _) {
"use strict";
- angular.module('search').component('crmSearch', {
+ angular.module('searchAdmin').component('crmSearch', {
bindings: {
entity: '=',
load: '<'
},
- templateUrl: '~/search/crmSearch.html',
+ templateUrl: '~/searchAdmin/crmSearch.html',
controller: function($scope, $element, $timeout, crmApi4, dialogService, searchMeta, formatForSelect2) {
var ts = $scope.ts = CRM.ts(),
ctrl = this;
loadFieldOptions();
};
- $scope.saveGroup = function() {
- var model = {
- title: '',
- description: '',
- visibility: 'User and User Admin Only',
- group_type: [],
- id: ctrl.load ? ctrl.load.id : null,
- api_entity: ctrl.entity,
- api_params: _.cloneDeep(angular.extend({}, ctrl.params, {version: 4}))
- };
- delete model.api_params.orderBy;
- if (ctrl.load && ctrl.load.api_params && ctrl.load.api_params.select && ctrl.load.api_params.select[0]) {
- model.api_params.select.unshift(ctrl.load.api_params.select[0]);
- }
- var options = CRM.utils.adjustDialogDefaults({
- autoOpen: false,
- title: ts('Save smart group')
- });
- dialogService.open('saveSearchDialog', '~/search/saveSmartGroup.html', model, options)
- .then(function() {
- if (ctrl.load) {
- ctrl.load.saved = true;
- }
- });
- };
}
});
</div>
<form>
- <div ng-include="'~/search/crmSearch/criteria.html'"></div>
- <div ng-include="'~/search/crmSearch/controls.html'"></div>
- <div ng-include="'~/search/crmSearch/debug.html'" ng-if="$ctrl.debug"></div>
- <div ng-include="'~/search/crmSearch/results.html'" class="crm-search-results"></div>
- <div ng-include="'~/search/crmSearch/pager.html'"></div>
+ <div ng-include="'~/searchAdmin/crmSearch/criteria.html'"></div>
+ <div ng-include="'~/searchAdmin/crmSearch/controls.html'"></div>
+ <div ng-include="'~/searchAdmin/crmSearch/debug.html'" ng-if="$ctrl.debug"></div>
+ <div ng-include="'~/searchAdmin/crmSearch/results.html'" class="crm-search-results"></div>
+ <div ng-include="'~/searchAdmin/crmSearch/pager.html'"></div>
</form>
</div>
</button>
<ul class="dropdown-menu">
<li ng-if=":: $ctrl.perm.editGroups">
- <a href ng-click="saveGroup()">{{:: ts('Smart Group') }}</a>
+ <a href save-smart-group load="$ctrl.load" entity="$ctrl.entity" params="$ctrl.params" ng-click="saveGroup()">{{:: ts('Smart Group') }}</a>
</li>
</ul>
</div>
<div class="navbar-form clearfix" ng-if="$ctrl.load">
<div class="form-group pull-right">
<label>{{ $ctrl.load.title }}</label>
- <button class="btn btn-default" ng-disabled="$ctrl.load.saved" ng-click="saveGroup()">{{ $ctrl.load.saved ? ts('Saved') : ts('Save') }}</button>
+ <button ng-if=":: $ctrl.perm.editGroups" save-smart-group load="$ctrl.load" entity="$ctrl.entity" params="$ctrl.params" class="btn btn-default" ng-disabled="$ctrl.load.saved" ng-click="saveGroup()">
+ {{ $ctrl.load.saved ? ts('Saved') : ts('Save') }}
+ </button>
</div>
</div>
<fieldset class="api4-clause-fieldset">
(function(angular, $, _) {
"use strict";
- angular.module('search').component('crmSearchClause', {
+ angular.module('searchAdmin').component('crmSearchClause', {
bindings: {
fields: '<',
clauses: '<',
label: '@',
deleteGroup: '&'
},
- templateUrl: '~/search/crmSearchClause.html',
+ templateUrl: '~/searchAdmin/crmSearchClause.html',
controller: function ($scope, $element, $timeout) {
var ts = $scope.ts = CRM.ts(),
ctrl = this;
(function(angular, $, _) {
"use strict";
- angular.module('search').component('crmSearchFunction', {
+ angular.module('searchAdmin').component('crmSearchFunction', {
bindings: {
expr: '=',
cat: '<'
},
- templateUrl: '~/search/crmSearchFunction.html',
+ templateUrl: '~/searchAdmin/crmSearchFunction.html',
controller: function($scope, formatForSelect2, searchMeta) {
var ts = $scope.ts = CRM.ts(),
ctrl = this;
(function(angular, $, _) {
"use strict";
- angular.module('search').directive('crmSearchValue', function($interval, searchMeta, formatForSelect2) {
+ angular.module('searchAdmin').directive('crmSearchValue', function($interval, searchMeta, formatForSelect2) {
return {
scope: {
data: '=crmSearchValue'
<menu>
<item>
<path>civicrm/search</path>
- <page_callback>CRM_Search_Page_Ang</page_callback>
+ <page_callback>CRM_Search_Page_Admin</page_callback>
<access_arguments>access CiviCRM</access_arguments>
</item>
</menu>