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