Commit | Line | Data |
---|---|---|
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 |
17 | use Civi\Api4\Utils\CoreUtil; |
18 | ||
6a488035 | 19 | /** |
b8c71ffa | 20 | * Recent items utility class. |
6a488035 TO |
21 | */ |
22 | class 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 | } |