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], | |
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 | } |