Refs: dev/core#3671 Fix regression involving CiviCRM Webform + Cases.
[civicrm-core.git] / CRM / Utils / Recent.php
CommitLineData
6a488035 1<?php
6a488035
TO
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/**
6a488035 13 * @package CRM
ca5cec67 14 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
15 */
16
0501e0ea
CW
17use Civi\Api4\Utils\CoreUtil;
18
6a488035 19/**
b8c71ffa 20 * Recent items utility class.
6a488035
TO
21 */
22class CRM_Utils_Recent {
23
24 /**
ac5f7c7f 25 * Store name
6a488035 26 *
ac5f7c7f 27 * @var string
6a488035 28 */
e144ca30
BT
29 const STORE_NAME = 'CRM_Utils_Recent';
30
31 /**
32 * Max number of recent items to store
33 *
34 * @var int
35 */
36 const MAX_ITEMS = 30;
6a488035
TO
37
38 /**
fe482240 39 * The list of recently viewed items.
6a488035
TO
40 *
41 * @var array
6a488035
TO
42 */
43 static private $_recent = NULL;
44
ac5f7c7f
NH
45 /**
46 * Maximum stack size
47 * @var int
48 */
074585b6 49 static private $_maxItems = 10;
7943980b 50
6a488035 51 /**
fe482240 52 * Initialize this class and set the static variables.
6a488035 53 */
00be9182 54 public static function initialize() {
43959a8a 55 $maxItemsSetting = Civi::settings()->get('recentItemsMaxCount');
074585b6 56 if (isset($maxItemsSetting) && $maxItemsSetting > 0 && $maxItemsSetting < self::MAX_ITEMS) {
136b401b
NH
57 self::$_maxItems = $maxItemsSetting;
58 }
6a488035
TO
59 if (!self::$_recent) {
60 $session = CRM_Core_Session::singleton();
61 self::$_recent = $session->get(self::STORE_NAME);
62 if (!self::$_recent) {
be2fb01f 63 self::$_recent = [];
6a488035
TO
64 }
65 }
66 }
67
68 /**
fe482240 69 * Return the recently viewed array.
6a488035 70 *
a6c01b45
CW
71 * @return array
72 * the recently viewed array
6a488035 73 */
00be9182 74 public static function &get() {
6a488035
TO
75 self::initialize();
76 return self::$_recent;
77 }
78
0501e0ea
CW
79 /**
80 * Create function used by the API - supplies defaults
81 *
82 * @param array $params
83 */
84 public static function create(array $params) {
85 $params['title'] = $params['title'] ?? self::getTitle($params['entity_type'], $params['entity_id']);
86 $params['view_url'] = $params['view_url'] ?? self::getUrl($params['entity_type'], $params['entity_id'], 'view');
87 $params['edit_url'] = $params['edit_url'] ?? self::getUrl($params['entity_type'], $params['entity_id'], 'update');
88 $params['delete_url'] = $params['delete_url'] ?? (empty($params['is_deleted']) ? self::getUrl($params['entity_type'], $params['entity_id'], 'delete') : NULL);
89 self::add($params['title'], $params['view_url'], $params['entity_id'], $params['entity_type'], $params['contact_id'] ?? NULL, NULL, $params);
90 return $params;
91 }
92
6a488035 93 /**
fe482240 94 * Add an item to the recent stack.
6a488035 95 *
77855840
TO
96 * @param string $title
97 * The title to display.
98 * @param string $url
99 * The link for the above title.
0501e0ea 100 * @param string $entityId
77855840 101 * Object id.
0501e0ea 102 * @param string $entityType
100fef9d 103 * @param int $contactId
0501e0ea 104 * Deprecated, probably unused param
100fef9d 105 * @param string $contactName
0501e0ea 106 * Deprecated, probably unused param
f4aaa82a 107 * @param array $others
6a488035 108 */
608e6658 109 public static function add(
a3e55d9c 110 $title,
6a488035 111 $url,
0501e0ea
CW
112 $entityId,
113 $entityType,
6a488035
TO
114 $contactId,
115 $contactName,
be2fb01f 116 $others = []
6a488035 117 ) {
0501e0ea
CW
118 $entityType = self::normalizeEntityType($entityType);
119
b1fdd118 120 // Abort if this entity type is not supported
0501e0ea 121 if (!self::isProviderEnabled($entityType)) {
136b401b
NH
122 return;
123 }
124
b1fdd118 125 // Ensure item is not already present in list
0501e0ea 126 self::removeItems(['entity_id' => $entityId, 'entity_type' => $entityType]);
6a488035
TO
127
128 if (!is_array($others)) {
be2fb01f 129 $others = [];
6a488035
TO
130 }
131
132 array_unshift(self::$_recent,
be2fb01f 133 [
6a488035 134 'title' => $title,
0501e0ea 135 // TODO: deprecate & remove "url" in favor of "view_url"
6a488035 136 'url' => $url,
0501e0ea
CW
137 'view_url' => $url,
138 // TODO: deprecate & remove "id" in favor of "entity_id"
139 'id' => $entityId,
140 'entity_id' => (int) $entityId,
141 // TODO: deprecate & remove "type" in favor of "entity_type"
142 'type' => $entityType,
143 'entity_type' => $entityType,
144 // Deprecated param
6a488035 145 'contact_id' => $contactId,
0501e0ea 146 // Param appears to be unused
6a488035 147 'contactName' => $contactName,
6b409353 148 'subtype' => $others['subtype'] ?? NULL,
0501e0ea
CW
149 // TODO: deprecate & remove "isDeleted" in favor of "is_deleted"
150 'isDeleted' => $others['is_deleted'] ?? $others['isDeleted'] ?? FALSE,
151 'is_deleted' => (bool) ($others['is_deleted'] ?? $others['isDeleted'] ?? FALSE),
152 // imageUrl is deprecated
6b409353 153 'image_url' => $others['imageUrl'] ?? NULL,
0501e0ea
CW
154 'edit_url' => $others['edit_url'] ?? $others['editUrl'] ?? NULL,
155 'delete_url' => $others['delete_url'] ?? $others['deleteUrl'] ?? NULL,
156 'icon' => $others['icon'] ?? self::getIcon($entityType, $entityId),
be2fb01f 157 ]
6a488035 158 );
136b401b 159
b1fdd118
CW
160 // Keep the list trimmed to max length
161 while (count(self::$_recent) > self::$_maxItems) {
6a488035
TO
162 array_pop(self::$_recent);
163 }
164
165 CRM_Utils_Hook::recent(self::$_recent);
166
b1fdd118 167 $session = CRM_Core_Session::singleton();
6a488035
TO
168 $session->set(self::STORE_NAME, self::$_recent);
169 }
170
3af57cde 171 /**
0501e0ea
CW
172 * Get default title for this item, based on the entity's `label_field`
173 *
174 * @param string $entityType
175 * @param int $entityId
176 * @return string|null
177 */
178 private static function getTitle($entityType, $entityId) {
179 $labelField = CoreUtil::getInfoItem($entityType, 'label_field');
180 $title = NULL;
181 if ($labelField) {
182 $record = civicrm_api4($entityType, 'get', [
183 'where' => [['id', '=', $entityId]],
184 'select' => [$labelField],
2c975c3d 185 'checkPermissions' => FALSE,
0501e0ea
CW
186 ], 0);
187 $title = $record[$labelField] ?? NULL;
188 }
189 return $title ?? (CoreUtil::getInfoItem($entityType, 'label_field'));
190 }
191
192 /**
193 * Get a link to view/update/delete a given entity.
194 *
195 * @param string $entityType
196 * @param int $entityId
197 * @param string $action
198 * Either 'view', 'update', or 'delete'
3af57cde
CW
199 * @return string|null
200 */
0501e0ea
CW
201 private static function getUrl($entityType, $entityId, $action) {
202 if ($action !== 'view') {
203 $check = civicrm_api4($entityType, 'checkAccess', [
204 'action' => $action,
205 'values' => ['id' => $entityId],
206 ], 0);
207 if (empty($check['access'])) {
208 return NULL;
3af57cde 209 }
3af57cde 210 }
0501e0ea
CW
211 $paths = (array) CoreUtil::getInfoItem($entityType, 'paths');
212 if (!empty($paths[$action])) {
213 return CRM_Utils_System::url(str_replace('[id]', $entityId, $paths[$action]));
214 }
215 return NULL;
216 }
217
218 /**
219 * @param $entityType
220 * @param $entityId
221 * @return string|null
222 */
223 private static function getIcon($entityType, $entityId) {
224 $icon = NULL;
225 $daoClass = CRM_Core_DAO_AllCoreTables::getFullName($entityType);
226 if ($daoClass) {
227 $icon = CRM_Core_DAO_AllCoreTables::getBAOClassName($daoClass)::getEntityIcon($entityType, $entityId);
3af57cde
CW
228 }
229 return $icon ?: 'fa-gear';
230 }
231
54043949
CW
232 /**
233 * Callback for hook_civicrm_post().
234 * @param \Civi\Core\Event\PostEvent $event
235 */
236 public static function on_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) {
0501e0ea
CW
237 if ($event->id && CRM_Core_Session::getLoggedInContactID()) {
238 $entityType = self::normalizeEntityType($event->entity);
239 if ($event->action === 'delete') {
240 // Is this an entity that might be in the recent items list?
241 $providersPermitted = Civi::settings()->get('recentItemsProviders') ?: array_keys(self::getProviders());
242 if (in_array($entityType, $providersPermitted)) {
243 self::del(['entity_id' => $event->id, 'entity_type' => $entityType]);
244 }
245 }
246 elseif ($event->action === 'edit') {
247 if (isset($event->object->is_deleted)) {
25049c09 248 \Civi\Api4\RecentItem::update(FALSE)
0501e0ea
CW
249 ->addWhere('entity_type', '=', $entityType)
250 ->addWhere('entity_id', '=', $event->id)
251 ->addValue('is_deleted', (bool) $event->object->is_deleted)
252 ->execute();
253 }
54043949
CW
254 }
255 }
256 }
257
6a488035 258 /**
b1fdd118
CW
259 * Remove items from the array that match given props
260 * @param array $props
6a488035 261 */
b1fdd118 262 private static function removeItems(array $props) {
6a488035 263 self::initialize();
6a488035 264
b1fdd118
CW
265 self::$_recent = array_filter(self::$_recent, function($item) use ($props) {
266 foreach ($props as $key => $val) {
0501e0ea 267 if (($item[$key] ?? NULL) != $val) {
a51ed717 268 return TRUE;
b1fdd118 269 }
6a488035 270 }
a51ed717 271 return FALSE;
b1fdd118
CW
272 });
273 }
6a488035 274
b1fdd118
CW
275 /**
276 * Delete item(s) from the recently-viewed list.
277 *
278 * @param array $removeItem
279 * Item to be removed.
280 */
281 public static function del($removeItem) {
282 self::removeItems($removeItem);
ab217754 283 CRM_Utils_Hook::recent(self::$_recent);
6a488035
TO
284 $session = CRM_Core_Session::singleton();
285 $session->set(self::STORE_NAME, self::$_recent);
286 }
287
288 /**
fe482240 289 * Delete an item from the recent stack.
6a488035 290 *
77855840 291 * @param string $id
b1fdd118 292 * @deprecated
6a488035 293 */
00be9182 294 public static function delContact($id) {
b1fdd118
CW
295 CRM_Core_Error::deprecatedFunctionWarning('del');
296 self::del(['contact_id' => $id]);
6a488035 297 }
96025800 298
ac5f7c7f
NH
299 /**
300 * Check if a provider is allowed to add stuff.
ab217754 301 * If corresponding setting is empty, all are allowed
136b401b 302 *
ac5f7c7f 303 * @param string $providerName
ab217754 304 * @return bool
ac5f7c7f
NH
305 */
306 public static function isProviderEnabled($providerName) {
136b401b
NH
307 $allowed = TRUE;
308
309 // Use core setting recentItemsProviders if configured
43959a8a 310 $providersPermitted = Civi::settings()->get('recentItemsProviders');
136b401b
NH
311 if ($providersPermitted) {
312 $allowed = in_array($providerName, $providersPermitted);
313 }
314 // Else allow
315 return $allowed;
ac5f7c7f
NH
316 }
317
0501e0ea
CW
318 /**
319 * @param string $entityType
320 * @return string
321 */
322 private static function normalizeEntityType($entityType) {
323 // Change Individual/Organization/Household to 'Contact'
324 if (in_array($entityType, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) {
325 return 'Contact';
326 }
327 return $entityType;
328 }
329
ac5f7c7f
NH
330 /**
331 * Gets the list of available providers to civi's recent items stack
ab217754 332 *
0501e0ea
CW
333 * TODO: Make this an option group so extensions can extend it.
334 *
ab217754 335 * @return array
ac5f7c7f
NH
336 */
337 public static function getProviders() {
be2fb01f 338 $providers = [
136b401b
NH
339 'Contact' => ts('Contacts'),
340 'Relationship' => ts('Relationships'),
341 'Activity' => ts('Activities'),
342 'Note' => ts('Notes'),
343 'Group' => ts('Groups'),
344 'Case' => ts('Cases'),
345 'Contribution' => ts('Contributions'),
346 'Participant' => ts('Participants'),
347 'Grant' => ts('Grants'),
348 'Membership' => ts('Memberships'),
349 'Pledge' => ts('Pledges'),
350 'Event' => ts('Events'),
351 'Campaign' => ts('Campaigns'),
be2fb01f 352 ];
ac5f7c7f 353
136b401b 354 return $providers;
ac5f7c7f 355 }
7943980b 356
6a488035 357}