Merge pull request #24022 from colemanw/afformFrontend
[civicrm-core.git] / CRM / Core / Action.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 * The core concept of the system is an action performed on an object. Typically this will be a "data model" object
14 * as specified in the API specs. We attempt to keep the number and type of actions consistent
15 * and similar across all objects (thus providing both reuse and standards)
16 *
17 * @package CRM
ca5cec67 18 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
19 */
20class CRM_Core_Action {
21
22 /**
23 * Different possible actions are defined here. Keep in sync with the
24 * constant from CRM_Core_Form for various modes.
25 *
9f266042 26 * @var int
6a488035 27 */
7da04cde 28 const
6a488035
TO
29 NONE = 0,
30 ADD = 1,
31 UPDATE = 2,
32 VIEW = 4,
33 DELETE = 8,
34 BROWSE = 16,
35 ENABLE = 32,
36 DISABLE = 64,
37 EXPORT = 128,
38 BASIC = 256,
39 ADVANCED = 512,
40 PREVIEW = 1024,
41 FOLLOWUP = 2048,
42 MAP = 4096,
43 PROFILE = 8192,
44 COPY = 16384,
45 RENEW = 32768,
46 DETACH = 65536,
47 REVERT = 131072,
353ffa53
TO
48 CLOSE = 262144,
49 REOPEN = 524288,
50 MAX_ACTION = 1048575;
6a488035
TO
51
52 //make sure MAX_ACTION = 2^n - 1 ( n = total number of actions )
53
54 /**
100fef9d 55 * Map the action names to the relevant constant. We perform
6a488035
TO
56 * bit manipulation operations so we can perform multiple
57 * actions on the same object if needed
58 *
518fa0ee 59 * @var array
6a488035 60 *
6a488035 61 */
518fa0ee 62 public static $_names = [
6a488035
TO
63 'add' => self::ADD,
64 'update' => self::UPDATE,
65 'view' => self::VIEW,
66 'delete' => self::DELETE,
67 'browse' => self::BROWSE,
68 'enable' => self::ENABLE,
69 'disable' => self::DISABLE,
70 'export' => self::EXPORT,
71 'preview' => self::PREVIEW,
72 'map' => self::MAP,
73 'copy' => self::COPY,
74 'profile' => self::PROFILE,
75 'renew' => self::RENEW,
76 'detach' => self::DETACH,
77 'revert' => self::REVERT,
353ffa53
TO
78 'close' => self::CLOSE,
79 'reopen' => self::REOPEN,
be2fb01f 80 ];
6a488035
TO
81
82 /**
100fef9d 83 * The flipped version of the names array, initialized when used
6a488035
TO
84 *
85 * @var array
6a488035 86 */
518fa0ee 87 public static $_description;
6a488035
TO
88
89 /**
eceb18cc 90 * Called by the request object to translate a string into a mask.
6a488035 91 *
6a0b768e
TO
92 * @param string $str
93 * The action to be resolved.
6a488035 94 *
a6c01b45
CW
95 * @return int
96 * the action mask corresponding to the input string
6a488035 97 */
00be9182 98 public static function resolve($str) {
6a488035
TO
99 $action = 0;
100 if ($str) {
101 $items = explode('|', $str);
102 $action = self::map($items);
103 }
104 return $action;
105 }
106
107 /**
108 * Given a string or an array of strings, determine the bitmask
109 * for this set of actions
110 *
6a0b768e
TO
111 * @param mixed $item
112 * Either a single string or an array of strings.
6a488035 113 *
a6c01b45
CW
114 * @return int
115 * the action mask corresponding to the input args
6a488035 116 */
00be9182 117 public static function map($item) {
6a488035
TO
118 $mask = 0;
119
120 if (is_array($item)) {
121 foreach ($item as $it) {
122 $mask |= self::mapItem($it);
123 }
124 return $mask;
125 }
126 else {
127 return self::mapItem($item);
128 }
129 }
130
131 /**
eceb18cc 132 * Given a string determine the bitmask for this specific string.
6a488035 133 *
6a0b768e
TO
134 * @param string $item
135 * The input action to process.
6a488035 136 *
a6c01b45
CW
137 * @return int
138 * the action mask corresponding to the input string
6a488035 139 */
00be9182 140 public static function mapItem($item) {
9c1bc317 141 $mask = self::$_names[trim($item)] ?? NULL;
6a488035
TO
142 return $mask ? $mask : 0;
143 }
144
145 /**
146 *
147 * Given an action mask, find the corresponding description
148 *
6a0b768e
TO
149 * @param int $mask
150 * The action mask.
6a488035 151 *
a6c01b45
CW
152 * @return string
153 * the corresponding action description
6a488035 154 */
00be9182 155 public static function description($mask) {
0e02cb01 156 if (!isset(self::$_description)) {
6a488035
TO
157 self::$_description = array_flip(self::$_names);
158 }
159
160 return CRM_Utils_Array::value($mask, self::$_description, 'NO DESCRIPTION SET');
161 }
162
163 /**
100fef9d 164 * Given a set of links and a mask, return the html action string for
6a488035
TO
165 * the links associated with the mask
166 *
6a0b768e
TO
167 * @param array $links
168 * The set of link items.
5e21e0f3 169 * @param int|null $mask
6a0b768e
TO
170 * The mask to be used. a null mask means all items.
171 * @param array $values
172 * The array of values for parameter substitution in the link items.
173 * @param string $extraULName
174 * Enclosed extra links in this UL.
175 * @param bool $enclosedAllInSingleUL
176 * Force to enclosed all links in single UL.
da6b46f4
EM
177 *
178 * @param null $op
179 * @param null $objectName
100fef9d 180 * @param int $objectId
7261f9ca
AH
181 * @param string $iconMode
182 * - `text`: even if `icon` is set for a link, display the `name`
183 * - `icon`: display only the `icon` for each link if it's available, and
184 * don't tuck anything under "more >"
185 * - `both`: if `icon` is available, display it next to the `name` for each
186 * link
6a488035 187 *
a6c01b45
CW
188 * @return string
189 * the html string
6a488035 190 */
d5cc0fc2 191 public static function formLink(
f9f40af3 192 $links,
6a488035
TO
193 $mask,
194 $values,
195 $extraULName = 'more',
196 $enclosedAllInSingleUL = FALSE,
197 $op = NULL,
198 $objectName = NULL,
7261f9ca
AH
199 $objectId = NULL,
200 $iconMode = 'text'
6a488035 201 ) {
6a488035
TO
202 if (empty($links)) {
203 return NULL;
204 }
205
a9b15f31
AH
206 // make links indexed sequentially instead of by bitmask
207 // otherwise it's next to impossible to reliably add new ones
be2fb01f 208 $seqLinks = [];
a9b15f31
AH
209 foreach ($links as $bit => $link) {
210 $link['bit'] = $bit;
211 $seqLinks[] = $link;
212 }
6a488035
TO
213
214 if ($op && $objectName && $objectId) {
a9b15f31 215 CRM_Utils_Hook::links($op, $objectName, $objectId, $seqLinks, $mask, $values);
6a488035
TO
216 }
217
be2fb01f 218 $url = [];
6a488035 219
a1c7d42f 220 foreach ($seqLinks as $i => $link) {
a9b15f31 221 if (!$mask || !array_key_exists('bit', $link) || ($mask & $link['bit'])) {
6a488035
TO
222 $extra = isset($link['extra']) ? self::replace($link['extra'], $values) : NULL;
223
63d76404 224 $frontend = isset($link['fe']);
6a488035 225
18d13c82 226 if (isset($link['qs']) && !CRM_Utils_System::isNull($link['qs'])) {
6a488035 227 $urlPath = CRM_Utils_System::url(self::replace($link['url'], $values),
8b25dde0 228 self::replace($link['qs'], $values), FALSE, NULL, TRUE, $frontend
6a488035
TO
229 );
230 }
231 else {
18d13c82 232 $urlPath = CRM_Utils_Array::value('url', $link, '#');
6a488035
TO
233 }
234
a1c7d42f 235 $classes = 'action-item crm-hover-button';
6a488035
TO
236 if (isset($link['ref'])) {
237 $classes .= ' ' . strtolower($link['ref']);
238 }
239
240 //get the user specified classes in.
241 if (isset($link['class'])) {
a1c7d42f 242 $className = is_array($link['class']) ? implode(' ', $link['class']) : $link['class'];
6a488035
TO
243 $classes .= ' ' . strtolower($className);
244 }
245
18d13c82
CW
246 if ($urlPath !== '#' && $frontend) {
247 $extra .= ' target="_blank"';
6a488035 248 }
a1c7d42f
CW
249 // Hack to make delete dialogs smaller
250 if (strpos($urlPath, '/delete') || strpos($urlPath, 'action=delete')) {
251 $classes .= " small-popup";
252 }
7261f9ca
AH
253
254 $linkContent = $link['name'];
255 if (!empty($link['icon'])) {
256 if ($iconMode == 'icon') {
257 $linkContent = CRM_Core_Page::crmIcon($link['icon'], $link['name'], TRUE, ['title' => '']);
258 }
259 elseif ($iconMode == 'both') {
260 $linkContent = CRM_Core_Page::crmIcon($link['icon']) . ' ' . $linkContent;
261 }
262 }
263
a1c7d42f 264 $url[] = sprintf('<a href="%s" class="%s" %s' . $extra . '>%s</a>',
18d13c82 265 $urlPath,
a1c7d42f
CW
266 $classes,
267 !empty($link['title']) ? "title='{$link['title']}' " : '',
7261f9ca 268 $linkContent
18d13c82 269 );
6a488035
TO
270 }
271 }
272
6a488035
TO
273 $mainLinks = $url;
274 if ($enclosedAllInSingleUL) {
275 $allLinks = '';
276 CRM_Utils_String::append($allLinks, '</li><li>', $mainLinks);
277 $allLinks = "{$extraULName}<ul class='panel'><li>{$allLinks}</li></ul>";
a1c7d42f 278 $result = "<span class='btn-slide crm-hover-button'>{$allLinks}</span>";
6a488035
TO
279 }
280 else {
281 $extra = '';
7261f9ca
AH
282 if ($iconMode != 'icon') {
283 $extraLinks = array_splice($url, 2);
284 if (count($extraLinks) > 1) {
285 $mainLinks = array_slice($url, 0, 2);
286 CRM_Utils_String::append($extra, '</li><li>', $extraLinks);
287 $extra = "{$extraULName}<ul class='panel'><li>{$extra}</li></ul>";
288 }
6a488035
TO
289 }
290 $resultLinks = '';
291 CRM_Utils_String::append($resultLinks, '', $mainLinks);
292 if ($extra) {
a1c7d42f 293 $result = "<span>{$resultLinks}</span><span class='btn-slide crm-hover-button'>{$extra}</span>";
6a488035
TO
294 }
295 else {
296 $result = "<span>{$resultLinks}</span>";
297 }
298 }
299
300 return $result;
301 }
302
29cff47e
MWMC
303 /**
304 * Given a set of links and a mask, return a filtered (by mask) array containing the final links with parsed values
305 * and calling hooks as appropriate.
306 * Use this when passing a set of action links to the API or to the form without adding html formatting.
307 *
308 * @param array $links
309 * The set of link items.
310 * @param int $mask
311 * The mask to be used. a null mask means all items.
312 * @param array $values
313 * The array of values for parameter substitution in the link items.
5e21e0f3
BT
314 * @param string|null $op
315 * @param string|null $objectName
29cff47e
MWMC
316 * @param int $objectId
317 *
318 * @return array|null
319 * The array describing each link
320 */
321 public static function filterLinks(
322 $links,
323 $mask,
324 $values,
325 $op = NULL,
326 $objectName = NULL,
327 $objectId = NULL
328 ) {
329 if (empty($links)) {
330 return NULL;
331 }
332
333 // make links indexed sequentially instead of by bitmask
334 // otherwise it's next to impossible to reliably add new ones
affcc9d2 335 $seqLinks = [];
29cff47e
MWMC
336 foreach ($links as $bit => $link) {
337 $link['bit'] = $bit;
338 $seqLinks[] = $link;
339 }
340
341 if ($op && $objectName && $objectId) {
342 CRM_Utils_Hook::links($op, $objectName, $objectId, $seqLinks, $mask, $values);
343 }
344
345 foreach ($seqLinks as $i => $link) {
346 if (!$mask || !array_key_exists('bit', $link) || ($mask & $link['bit'])) {
347 $seqLinks[$i]['extra'] = isset($link['extra']) ? self::replace($link['extra'], $values) : NULL;
348
349 if (isset($link['qs']) && !CRM_Utils_System::isNull($link['qs'])) {
350 $seqLinks[$i]['url'] = self::replace($link['url'], $values);
351 $seqLinks[$i]['qs'] = self::replace($link['qs'], $values);
352 }
353 }
354 else {
355 unset($seqLinks[$i]);
356 }
357 }
358
359 return $seqLinks;
360 }
361
6a488035 362 /**
100fef9d 363 * Given a string and an array of values, substitute the real values
6a488035
TO
364 * in the placeholder in the str in the CiviCRM format
365 *
6a0b768e
TO
366 * @param string $str
367 * The string to be replaced.
368 * @param array $values
369 * The array of values for parameter substitution in the str.
6a488035 370 *
a6c01b45
CW
371 * @return string
372 * the substituted string
6a488035 373 */
00be9182 374 public static function &replace(&$str, &$values) {
6a488035 375 foreach ($values as $n => $v) {
16fd31ae 376 $str = str_replace("%%$n%%", ($v ?? ''), ($str ?? ''));
6a488035
TO
377 }
378 return $str;
379 }
380
381 /**
100fef9d 382 * Get the mask for a permission (view, edit or null)
6a488035 383 *
ad37ac8e 384 * @param array $permissions
385 *
a6c01b45 386 * @return int
ad37ac8e 387 * The mask for the above permission
6a488035 388 */
00be9182 389 public static function mask($permissions) {
6a488035
TO
390 $mask = NULL;
391 if (!is_array($permissions) || CRM_Utils_System::isNull($permissions)) {
392 return $mask;
393 }
394 //changed structure since we are handling delete separately - CRM-4418
395 if (in_array(CRM_Core_Permission::VIEW, $permissions)) {
396 $mask |= self::VIEW | self::EXPORT | self::BASIC | self::ADVANCED | self::BROWSE | self::MAP | self::PROFILE;
397 }
398 if (in_array(CRM_Core_Permission::DELETE, $permissions)) {
399 $mask |= self::DELETE;
400 }
401 if (in_array(CRM_Core_Permission::EDIT, $permissions)) {
402 //make sure we make self::MAX_ACTION = 2^n - 1
403 //if we add more actions; ( n = total number of actions )
404 $mask |= (self::MAX_ACTION & ~self::DELETE);
405 }
406
407 return $mask;
408 }
96025800 409
6a488035 410}