Merge pull request #23461 from eileenmcnaughton/import_woohoo
[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],
185 ], 0);
186 $title = $record[$labelField] ?? NULL;
187 }
188 return $title ?? (CoreUtil::getInfoItem($entityType, 'label_field'));
189 }
190
191 /**
192 * Get a link to view/update/delete a given entity.
193 *
194 * @param string $entityType
195 * @param int $entityId
196 * @param string $action
197 * Either 'view', 'update', or 'delete'
3af57cde
CW
198 * @return string|null
199 */
0501e0ea
CW
200 private static function getUrl($entityType, $entityId, $action) {
201 if ($action !== 'view') {
202 $check = civicrm_api4($entityType, 'checkAccess', [
203 'action' => $action,
204 'values' => ['id' => $entityId],
205 ], 0);
206 if (empty($check['access'])) {
207 return NULL;
3af57cde 208 }
3af57cde 209 }
0501e0ea
CW
210 $paths = (array) CoreUtil::getInfoItem($entityType, 'paths');
211 if (!empty($paths[$action])) {
212 return CRM_Utils_System::url(str_replace('[id]', $entityId, $paths[$action]));
213 }
214 return NULL;
215 }
216
217 /**
218 * @param $entityType
219 * @param $entityId
220 * @return string|null
221 */
222 private static function getIcon($entityType, $entityId) {
223 $icon = NULL;
224 $daoClass = CRM_Core_DAO_AllCoreTables::getFullName($entityType);
225 if ($daoClass) {
226 $icon = CRM_Core_DAO_AllCoreTables::getBAOClassName($daoClass)::getEntityIcon($entityType, $entityId);
3af57cde
CW
227 }
228 return $icon ?: 'fa-gear';
229 }
230
54043949
CW
231 /**
232 * Callback for hook_civicrm_post().
233 * @param \Civi\Core\Event\PostEvent $event
234 */
235 public static function on_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) {
0501e0ea
CW
236 if ($event->id && CRM_Core_Session::getLoggedInContactID()) {
237 $entityType = self::normalizeEntityType($event->entity);
238 if ($event->action === 'delete') {
239 // Is this an entity that might be in the recent items list?
240 $providersPermitted = Civi::settings()->get('recentItemsProviders') ?: array_keys(self::getProviders());
241 if (in_array($entityType, $providersPermitted)) {
242 self::del(['entity_id' => $event->id, 'entity_type' => $entityType]);
243 }
244 }
245 elseif ($event->action === 'edit') {
246 if (isset($event->object->is_deleted)) {
247 \Civi\Api4\RecentItem::update()
248 ->addWhere('entity_type', '=', $entityType)
249 ->addWhere('entity_id', '=', $event->id)
250 ->addValue('is_deleted', (bool) $event->object->is_deleted)
251 ->execute();
252 }
54043949
CW
253 }
254 }
255 }
256
6a488035 257 /**
b1fdd118
CW
258 * Remove items from the array that match given props
259 * @param array $props
6a488035 260 */
b1fdd118 261 private static function removeItems(array $props) {
6a488035 262 self::initialize();
6a488035 263
b1fdd118
CW
264 self::$_recent = array_filter(self::$_recent, function($item) use ($props) {
265 foreach ($props as $key => $val) {
0501e0ea 266 if (($item[$key] ?? NULL) != $val) {
a51ed717 267 return TRUE;
b1fdd118 268 }
6a488035 269 }
a51ed717 270 return FALSE;
b1fdd118
CW
271 });
272 }
6a488035 273
b1fdd118
CW
274 /**
275 * Delete item(s) from the recently-viewed list.
276 *
277 * @param array $removeItem
278 * Item to be removed.
279 */
280 public static function del($removeItem) {
281 self::removeItems($removeItem);
ab217754 282 CRM_Utils_Hook::recent(self::$_recent);
6a488035
TO
283 $session = CRM_Core_Session::singleton();
284 $session->set(self::STORE_NAME, self::$_recent);
285 }
286
287 /**
fe482240 288 * Delete an item from the recent stack.
6a488035 289 *
77855840 290 * @param string $id
b1fdd118 291 * @deprecated
6a488035 292 */
00be9182 293 public static function delContact($id) {
b1fdd118
CW
294 CRM_Core_Error::deprecatedFunctionWarning('del');
295 self::del(['contact_id' => $id]);
6a488035 296 }
96025800 297
ac5f7c7f
NH
298 /**
299 * Check if a provider is allowed to add stuff.
ab217754 300 * If corresponding setting is empty, all are allowed
136b401b 301 *
ac5f7c7f 302 * @param string $providerName
ab217754 303 * @return bool
ac5f7c7f
NH
304 */
305 public static function isProviderEnabled($providerName) {
136b401b
NH
306 $allowed = TRUE;
307
308 // Use core setting recentItemsProviders if configured
43959a8a 309 $providersPermitted = Civi::settings()->get('recentItemsProviders');
136b401b
NH
310 if ($providersPermitted) {
311 $allowed = in_array($providerName, $providersPermitted);
312 }
313 // Else allow
314 return $allowed;
ac5f7c7f
NH
315 }
316
0501e0ea
CW
317 /**
318 * @param string $entityType
319 * @return string
320 */
321 private static function normalizeEntityType($entityType) {
322 // Change Individual/Organization/Household to 'Contact'
323 if (in_array($entityType, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) {
324 return 'Contact';
325 }
326 return $entityType;
327 }
328
ac5f7c7f
NH
329 /**
330 * Gets the list of available providers to civi's recent items stack
ab217754 331 *
0501e0ea
CW
332 * TODO: Make this an option group so extensions can extend it.
333 *
ab217754 334 * @return array
ac5f7c7f
NH
335 */
336 public static function getProviders() {
be2fb01f 337 $providers = [
136b401b
NH
338 'Contact' => ts('Contacts'),
339 'Relationship' => ts('Relationships'),
340 'Activity' => ts('Activities'),
341 'Note' => ts('Notes'),
342 'Group' => ts('Groups'),
343 'Case' => ts('Cases'),
344 'Contribution' => ts('Contributions'),
345 'Participant' => ts('Participants'),
346 'Grant' => ts('Grants'),
347 'Membership' => ts('Memberships'),
348 'Pledge' => ts('Pledges'),
349 'Event' => ts('Events'),
350 'Campaign' => ts('Campaigns'),
be2fb01f 351 ];
ac5f7c7f 352
136b401b 353 return $providers;
ac5f7c7f 354 }
7943980b 355
6a488035 356}