3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
14 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 use Civi\Api4\Utils\CoreUtil
;
20 * Recent items utility class.
22 class CRM_Utils_Recent
{
29 const STORE_NAME
= 'CRM_Utils_Recent';
32 * Max number of recent items to store
39 * The list of recently viewed items.
43 static private $_recent = NULL;
49 static private $_maxItems = 10;
52 * Initialize this class and set the static variables.
54 public static function initialize() {
55 $maxItemsSetting = Civi
::settings()->get('recentItemsMaxCount');
56 if (isset($maxItemsSetting) && $maxItemsSetting > 0 && $maxItemsSetting < self
::MAX_ITEMS
) {
57 self
::$_maxItems = $maxItemsSetting;
59 if (!self
::$_recent) {
60 $session = CRM_Core_Session
::singleton();
61 self
::$_recent = $session->get(self
::STORE_NAME
);
62 if (!self
::$_recent) {
69 * Return the recently viewed array.
72 * the recently viewed array
74 public static function &get() {
76 return self
::$_recent;
80 * Create function used by the API - supplies defaults
82 * @param array $params
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);
94 * Add an item to the recent stack.
96 * @param string $title
97 * The title to display.
99 * The link for the above title.
100 * @param string $entityId
102 * @param string $entityType
103 * @param int $contactId
104 * Deprecated, probably unused param
105 * @param string $contactName
106 * Deprecated, probably unused param
107 * @param array $others
109 public static function add(
118 $entityType = self
::normalizeEntityType($entityType);
120 // Abort if this entity type is not supported
121 if (!self
::isProviderEnabled($entityType)) {
125 // Ensure item is not already present in list
126 self
::removeItems(['entity_id' => $entityId, 'entity_type' => $entityType]);
128 if (!is_array($others)) {
132 array_unshift(self
::$_recent,
135 // TODO: deprecate & remove "url" in favor of "view_url"
138 // TODO: deprecate & remove "id" in favor of "entity_id"
140 'entity_id' => (int) $entityId,
141 // TODO: deprecate & remove "type" in favor of "entity_type"
142 'type' => $entityType,
143 'entity_type' => $entityType,
145 'contact_id' => $contactId,
146 // Param appears to be unused
147 'contactName' => $contactName,
148 'subtype' => $others['subtype'] ??
NULL,
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
153 'image_url' => $others['imageUrl'] ??
NULL,
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),
160 // Keep the list trimmed to max length
161 while (count(self
::$_recent) > self
::$_maxItems) {
162 array_pop(self
::$_recent);
165 CRM_Utils_Hook
::recent(self
::$_recent);
167 $session = CRM_Core_Session
::singleton();
168 $session->set(self
::STORE_NAME
, self
::$_recent);
172 * Get default title for this item, based on the entity's `label_field`
174 * @param string $entityType
175 * @param int $entityId
176 * @return string|null
178 private static function getTitle($entityType, $entityId) {
179 $labelField = CoreUtil
::getInfoItem($entityType, 'label_field');
182 $record = civicrm_api4($entityType, 'get', [
183 'where' => [['id', '=', $entityId]],
184 'select' => [$labelField],
186 $title = $record[$labelField] ??
NULL;
188 return $title ??
(CoreUtil
::getInfoItem($entityType, 'label_field'));
192 * Get a link to view/update/delete a given entity.
194 * @param string $entityType
195 * @param int $entityId
196 * @param string $action
197 * Either 'view', 'update', or 'delete'
198 * @return string|null
200 private static function getUrl($entityType, $entityId, $action) {
201 if ($action !== 'view') {
202 $check = civicrm_api4($entityType, 'checkAccess', [
204 'values' => ['id' => $entityId],
206 if (empty($check['access'])) {
210 $paths = (array) CoreUtil
::getInfoItem($entityType, 'paths');
211 if (!empty($paths[$action])) {
212 return CRM_Utils_System
::url(str_replace('[id]', $entityId, $paths[$action]));
220 * @return string|null
222 private static function getIcon($entityType, $entityId) {
224 $daoClass = CRM_Core_DAO_AllCoreTables
::getFullName($entityType);
226 $icon = CRM_Core_DAO_AllCoreTables
::getBAOClassName($daoClass)::getEntityIcon($entityType, $entityId);
228 return $icon ?
: 'fa-gear';
232 * Callback for hook_civicrm_post().
233 * @param \Civi\Core\Event\PostEvent $event
235 public static function on_hook_civicrm_post(\Civi\Core\Event\PostEvent
$event) {
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]);
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
)
258 * Remove items from the array that match given props
259 * @param array $props
261 private static function removeItems(array $props) {
264 self
::$_recent = array_filter(self
::$_recent, function($item) use ($props) {
265 foreach ($props as $key => $val) {
266 if (($item[$key] ??
NULL) != $val) {
275 * Delete item(s) from the recently-viewed list.
277 * @param array $removeItem
278 * Item to be removed.
280 public static function del($removeItem) {
281 self
::removeItems($removeItem);
282 CRM_Utils_Hook
::recent(self
::$_recent);
283 $session = CRM_Core_Session
::singleton();
284 $session->set(self
::STORE_NAME
, self
::$_recent);
288 * Delete an item from the recent stack.
293 public static function delContact($id) {
294 CRM_Core_Error
::deprecatedFunctionWarning('del');
295 self
::del(['contact_id' => $id]);
299 * Check if a provider is allowed to add stuff.
300 * If corresponding setting is empty, all are allowed
302 * @param string $providerName
305 public static function isProviderEnabled($providerName) {
308 // Use core setting recentItemsProviders if configured
309 $providersPermitted = Civi
::settings()->get('recentItemsProviders');
310 if ($providersPermitted) {
311 $allowed = in_array($providerName, $providersPermitted);
318 * @param string $entityType
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)) {
330 * Gets the list of available providers to civi's recent items stack
332 * TODO: Make this an option group so extensions can extend it.
336 public static function getProviders() {
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'),