Merge pull request #18770 from civicrm/5.31
[civicrm-core.git] / ext / search / CRM / Search / Page / Ang.php
1 <?php
2
3 class CRM_Search_Page_Ang extends CRM_Core_Page {
4 /**
5 * @var string[]
6 */
7 private $loadOptions = ['id', 'name', 'label', 'description', 'color', 'icon'];
8
9 /**
10 * @var array
11 */
12 private $schema = [];
13
14 /**
15 * @var string[]
16 */
17 private $allowedEntities = [];
18
19 public function run() {
20 $breadCrumb = [
21 'title' => ts('Search'),
22 'url' => CRM_Utils_System::url('civicrm/search'),
23 ];
24 CRM_Utils_System::appendBreadCrumb([$breadCrumb]);
25
26 $this->getSchema();
27
28 // If user does not have permission to search any entity, bye bye.
29 if (!$this->allowedEntities) {
30 CRM_Utils_System::permissionDenied();
31 }
32
33 // Add client-side vars for the search UI
34 $vars = [
35 'operators' => CRM_Utils_Array::makeNonAssociative($this->getOperators()),
36 'schema' => $this->schema,
37 'links' => $this->getLinks(),
38 'loadOptions' => $this->loadOptions,
39 'actions' => $this->getActions(),
40 'functions' => CRM_Api4_Page_Api4Explorer::getSqlFunctions(),
41 ];
42
43 Civi::resources()
44 ->addPermissions(['edit groups', 'administer reserved groups'])
45 ->addBundle('bootstrap3')
46 ->addVars('search', $vars);
47
48 // Load angular module
49 $loader = new Civi\Angular\AngularLoader();
50 $loader->setModules(['search']);
51 $loader->setPageName('civicrm/search');
52 $loader->useApp([
53 'defaultRoute' => '/create/Contact',
54 ]);
55 $loader->load();
56 parent::run();
57 }
58
59 /**
60 * @return string[]
61 */
62 private function getOperators() {
63 return [
64 '=' => '=',
65 '!=' => '≠',
66 '>' => '>',
67 '<' => '<',
68 '>=' => '≥',
69 '<=' => '≤',
70 'CONTAINS' => ts('Contains'),
71 'IN' => ts('Is In'),
72 'NOT IN' => ts('Not In'),
73 'LIKE' => ts('Is Like'),
74 'NOT LIKE' => ts('Not Like'),
75 'BETWEEN' => ts('Is Between'),
76 'NOT BETWEEN' => ts('Not Between'),
77 'IS NULL' => ts('Is Null'),
78 'IS NOT NULL' => ts('Not Null'),
79 ];
80 }
81
82 /**
83 * Populates $this->schema & $this->allowedEntities
84 */
85 private function getSchema() {
86 $schema = \Civi\Api4\Entity::get()
87 ->addSelect('name', 'title', 'titlePlural', 'description', 'icon')
88 ->addWhere('name', '!=', 'Entity')
89 ->addOrderBy('titlePlural')
90 ->setChain([
91 'get' => ['$name', 'getActions', ['where' => [['name', '=', 'get']]], ['params']],
92 ])->execute();
93 $getFields = ['name', 'label', 'description', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'fk_entity'];
94 foreach ($schema as $entity) {
95 // Skip if entity doesn't have a 'get' action or the user doesn't have permission to use get
96 if ($entity['get']) {
97 // Get fields and pre-load options for certain prominent entities
98 $loadOptions = in_array($entity['name'], ['Contact', 'Group']) ? $this->loadOptions : FALSE;
99 if ($loadOptions) {
100 $entity['optionsLoaded'] = TRUE;
101 }
102 $entity['fields'] = civicrm_api4($entity['name'], 'getFields', [
103 'select' => $getFields,
104 'where' => [['permission', 'IS NULL']],
105 'orderBy' => ['label'],
106 'loadOptions' => $loadOptions,
107 ]);
108 // Get the names of params this entity supports (minus some obvious ones)
109 $params = $entity['get'][0];
110 CRM_Utils_Array::remove($params, 'checkPermissions', 'debug', 'chain', 'language');
111 unset($entity['get']);
112 $this->schema[] = ['params' => array_keys($params)] + array_filter($entity);
113 $this->allowedEntities[] = $entity['name'];
114 }
115 }
116 }
117
118 /**
119 * @return array
120 */
121 private function getLinks() {
122 $results = [];
123 $keys = array_flip(['alias', 'entity', 'joinType']);
124 foreach (civicrm_api4('Entity', 'getLinks', ['where' => [['entity', 'IN', $this->allowedEntities]]], ['entity' => 'links']) as $entity => $links) {
125 $entityLinks = [];
126 foreach ($links as $link) {
127 if (!empty($link['entity']) && in_array($link['entity'], $this->allowedEntities)) {
128 // Use entity.alias as array key to avoid duplicates
129 $entityLinks[$link['entity'] . $link['alias']] = array_intersect_key($link, $keys);
130 }
131 }
132 $results[$entity] = array_values($entityLinks);
133 }
134 return array_filter($results);
135 }
136
137 /**
138 * @return array[]
139 */
140 private function getActions() {
141 // Note: the placeholder %1 will be replaced with entity name on the clientside
142 $actions = [
143 'export' => [
144 'title' => ts('Export %1'),
145 'icon' => 'fa-file-excel-o',
146 'entities' => array_keys(CRM_Export_BAO_Export::getComponents()),
147 'crmPopup' => [
148 'path' => "'civicrm/export/standalone'",
149 'query' => "{entity: entity, id: ids.join(',')}",
150 ],
151 ],
152 'update' => [
153 'title' => ts('Update %1'),
154 'icon' => 'fa-save',
155 'entities' => [],
156 'uiDialog' => ['templateUrl' => '~/search/crmSearchActions/crmSearchActionUpdate.html'],
157 ],
158 'delete' => [
159 'title' => ts('Delete %1'),
160 'icon' => 'fa-trash',
161 'entities' => [],
162 'uiDialog' => ['templateUrl' => '~/search/crmSearchActions/crmSearchActionDelete.html'],
163 ],
164 ];
165
166 // Check permissions for update & delete actions
167 foreach ($this->allowedEntities as $entity) {
168 $result = civicrm_api4($entity, 'getActions', [
169 'where' => [['name', 'IN', ['update', 'delete']]],
170 ], ['name']);
171 foreach ($result as $action) {
172 // Contacts have their own delete action
173 if (!($entity === 'Contact' && $action === 'delete')) {
174 $actions[$action]['entities'][] = $entity;
175 }
176 }
177 }
178
179 // Add contact tasks which support standalone mode (with a 'url' property)
180 $contactTasks = CRM_Contact_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission());
181 foreach (CRM_Contact_Task::tasks() as $id => $task) {
182 if (isset($contactTasks[$id]) && !empty($task['url'])) {
183 $actions['contact.' . $id] = [
184 'title' => $task['title'],
185 'entities' => ['Contact'],
186 'icon' => $task['icon'] ?? 'fa-gear',
187 'crmPopup' => [
188 'path' => "'{$task['url']}'",
189 'query' => "{cids: ids.join(',')}",
190 ],
191 ];
192 }
193 }
194
195 return $actions;
196 }
197
198 }