* @copyright CiviCRM LLC https://civicrm.org/licensing
*/
+use Civi\Api4\Utils\CoreUtil;
+
/**
* Recent items utility class.
*/
*
* @var string
*/
- const MAX_ITEMS = 30, STORE_NAME = 'CRM_Utils_Recent';
+ const STORE_NAME = 'CRM_Utils_Recent';
+
+ /**
+ * Max number of recent items to store
+ *
+ * @var int
+ */
+ const MAX_ITEMS = 30;
/**
* The list of recently viewed items.
return self::$_recent;
}
+ /**
+ * Create function used by the API - supplies defaults
+ *
+ * @param array $params
+ */
+ public static function create(array $params) {
+ $params['title'] = $params['title'] ?? self::getTitle($params['entity_type'], $params['entity_id']);
+ $params['view_url'] = $params['view_url'] ?? self::getUrl($params['entity_type'], $params['entity_id'], 'view');
+ $params['edit_url'] = $params['edit_url'] ?? self::getUrl($params['entity_type'], $params['entity_id'], 'update');
+ $params['delete_url'] = $params['delete_url'] ?? (empty($params['is_deleted']) ? self::getUrl($params['entity_type'], $params['entity_id'], 'delete') : NULL);
+ self::add($params['title'], $params['view_url'], $params['entity_id'], $params['entity_type'], $params['contact_id'] ?? NULL, NULL, $params);
+ return $params;
+ }
+
/**
* Add an item to the recent stack.
*
* The title to display.
* @param string $url
* The link for the above title.
- * @param string $id
+ * @param string $entityId
* Object id.
- * @param $type
+ * @param string $entityType
* @param int $contactId
+ * Deprecated, probably unused param
* @param string $contactName
+ * Deprecated, probably unused param
* @param array $others
*/
public static function add(
$title,
$url,
- $id,
- $type,
+ $entityId,
+ $entityType,
$contactId,
$contactName,
$others = []
) {
- self::initialize();
+ $entityType = self::normalizeEntityType($entityType);
- if (!self::isProviderEnabled($type)) {
+ // Abort if this entity type is not supported
+ if (!self::isProviderEnabled($entityType)) {
return;
}
- $session = CRM_Core_Session::singleton();
-
- // make sure item is not already present in list
- for ($i = 0; $i < count(self::$_recent); $i++) {
- if (self::$_recent[$i]['type'] === $type && self::$_recent[$i]['id'] == $id) {
- // delete item from array
- array_splice(self::$_recent, $i, 1);
- break;
- }
- }
+ // Ensure item is not already present in list
+ self::removeItems(['entity_id' => $entityId, 'entity_type' => $entityType]);
if (!is_array($others)) {
$others = [];
array_unshift(self::$_recent,
[
'title' => $title,
+ // TODO: deprecate & remove "url" in favor of "view_url"
'url' => $url,
- 'id' => $id,
- 'type' => $type,
+ 'view_url' => $url,
+ // TODO: deprecate & remove "id" in favor of "entity_id"
+ 'id' => $entityId,
+ 'entity_id' => (int) $entityId,
+ // TODO: deprecate & remove "type" in favor of "entity_type"
+ 'type' => $entityType,
+ 'entity_type' => $entityType,
+ // Deprecated param
'contact_id' => $contactId,
+ // Param appears to be unused
'contactName' => $contactName,
'subtype' => $others['subtype'] ?? NULL,
- 'isDeleted' => $others['isDeleted'] ?? FALSE,
+ // TODO: deprecate & remove "isDeleted" in favor of "is_deleted"
+ 'isDeleted' => $others['is_deleted'] ?? $others['isDeleted'] ?? FALSE,
+ 'is_deleted' => (bool) ($others['is_deleted'] ?? $others['isDeleted'] ?? FALSE),
+ // imageUrl is deprecated
'image_url' => $others['imageUrl'] ?? NULL,
- 'edit_url' => $others['editUrl'] ?? NULL,
- 'delete_url' => $others['deleteUrl'] ?? NULL,
+ 'edit_url' => $others['edit_url'] ?? $others['editUrl'] ?? NULL,
+ 'delete_url' => $others['delete_url'] ?? $others['deleteUrl'] ?? NULL,
+ 'icon' => $others['icon'] ?? self::getIcon($entityType, $entityId),
]
);
- if (count(self::$_recent) > self::$_maxItems) {
+ // Keep the list trimmed to max length
+ while (count(self::$_recent) > self::$_maxItems) {
array_pop(self::$_recent);
}
CRM_Utils_Hook::recent(self::$_recent);
+ $session = CRM_Core_Session::singleton();
$session->set(self::STORE_NAME, self::$_recent);
}
/**
- * Delete an item from the recent stack.
+ * Get default title for this item, based on the entity's `label_field`
*
- * @param array $recentItem
- * Array of the recent Item to be removed.
+ * @param string $entityType
+ * @param int $entityId
+ * @return string|null
*/
- public static function del($recentItem) {
- self::initialize();
- $tempRecent = self::$_recent;
+ private static function getTitle($entityType, $entityId) {
+ $labelField = CoreUtil::getInfoItem($entityType, 'label_field');
+ $title = NULL;
+ if ($labelField) {
+ $record = civicrm_api4($entityType, 'get', [
+ 'where' => [['id', '=', $entityId]],
+ 'select' => [$labelField],
+ ], 0);
+ $title = $record[$labelField] ?? NULL;
+ }
+ return $title ?? (CoreUtil::getInfoItem($entityType, 'label_field'));
+ }
+
+ /**
+ * Get a link to view/update/delete a given entity.
+ *
+ * @param string $entityType
+ * @param int $entityId
+ * @param string $action
+ * Either 'view', 'update', or 'delete'
+ * @return string|null
+ */
+ private static function getUrl($entityType, $entityId, $action) {
+ if ($action !== 'view') {
+ $check = civicrm_api4($entityType, 'checkAccess', [
+ 'action' => $action,
+ 'values' => ['id' => $entityId],
+ ], 0);
+ if (empty($check['access'])) {
+ return NULL;
+ }
+ }
+ $paths = (array) CoreUtil::getInfoItem($entityType, 'paths');
+ if (!empty($paths[$action])) {
+ return CRM_Utils_System::url(str_replace('[id]', $entityId, $paths[$action]));
+ }
+ return NULL;
+ }
- self::$_recent = [];
+ /**
+ * @param $entityType
+ * @param $entityId
+ * @return string|null
+ */
+ private static function getIcon($entityType, $entityId) {
+ $icon = NULL;
+ $daoClass = CRM_Core_DAO_AllCoreTables::getFullName($entityType);
+ if ($daoClass) {
+ $icon = CRM_Core_DAO_AllCoreTables::getBAOClassName($daoClass)::getEntityIcon($entityType, $entityId);
+ }
+ return $icon ?: 'fa-gear';
+ }
- // make sure item is not already present in list
- for ($i = 0; $i < count($tempRecent); $i++) {
- if (!($tempRecent[$i]['id'] == $recentItem['id'] &&
- $tempRecent[$i]['type'] == $recentItem['type']
- )
- ) {
- self::$_recent[] = $tempRecent[$i];
+ /**
+ * Callback for hook_civicrm_post().
+ * @param \Civi\Core\Event\PostEvent $event
+ */
+ public static function on_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) {
+ if ($event->id && CRM_Core_Session::getLoggedInContactID()) {
+ $entityType = self::normalizeEntityType($event->entity);
+ if ($event->action === 'delete') {
+ // Is this an entity that might be in the recent items list?
+ $providersPermitted = Civi::settings()->get('recentItemsProviders') ?: array_keys(self::getProviders());
+ if (in_array($entityType, $providersPermitted)) {
+ self::del(['entity_id' => $event->id, 'entity_type' => $entityType]);
+ }
+ }
+ elseif ($event->action === 'edit') {
+ if (isset($event->object->is_deleted)) {
+ \Civi\Api4\RecentItem::update()
+ ->addWhere('entity_type', '=', $entityType)
+ ->addWhere('entity_id', '=', $event->id)
+ ->addValue('is_deleted', (bool) $event->object->is_deleted)
+ ->execute();
+ }
}
}
+ }
+
+ /**
+ * Remove items from the array that match given props
+ * @param array $props
+ */
+ private static function removeItems(array $props) {
+ self::initialize();
+ self::$_recent = array_filter(self::$_recent, function($item) use ($props) {
+ foreach ($props as $key => $val) {
+ if (($item[$key] ?? NULL) != $val) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+ });
+ }
+
+ /**
+ * Delete item(s) from the recently-viewed list.
+ *
+ * @param array $removeItem
+ * Item to be removed.
+ */
+ public static function del($removeItem) {
+ self::removeItems($removeItem);
CRM_Utils_Hook::recent(self::$_recent);
$session = CRM_Core_Session::singleton();
$session->set(self::STORE_NAME, self::$_recent);
* Delete an item from the recent stack.
*
* @param string $id
- * Contact id that had to be removed.
+ * @deprecated
*/
public static function delContact($id) {
- self::initialize();
-
- $tempRecent = self::$_recent;
-
- self::$_recent = [];
-
- // rebuild recent.
- for ($i = 0; $i < count($tempRecent); $i++) {
- // don't include deleted contact in recent.
- if (CRM_Utils_Array::value('contact_id', $tempRecent[$i]) == $id) {
- continue;
- }
- self::$_recent[] = $tempRecent[$i];
- }
-
- CRM_Utils_Hook::recent(self::$_recent);
- $session = CRM_Core_Session::singleton();
- $session->set(self::STORE_NAME, self::$_recent);
+ CRM_Core_Error::deprecatedFunctionWarning('del');
+ self::del(['contact_id' => $id]);
}
/**
* @return bool
*/
public static function isProviderEnabled($providerName) {
-
- // Join contact types to providerName 'Contact'
- $contactTypes = CRM_Contact_BAO_ContactType::contactTypes(TRUE);
- if (in_array($providerName, $contactTypes)) {
- $providerName = 'Contact';
- }
$allowed = TRUE;
// Use core setting recentItemsProviders if configured
return $allowed;
}
+ /**
+ * @param string $entityType
+ * @return string
+ */
+ private static function normalizeEntityType($entityType) {
+ // Change Individual/Organization/Household to 'Contact'
+ if (in_array($entityType, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) {
+ return 'Contact';
+ }
+ return $entityType;
+ }
+
/**
* Gets the list of available providers to civi's recent items stack
*
+ * TODO: Make this an option group so extensions can extend it.
+ *
* @return array
*/
public static function getProviders() {