APIv4 - Make Activity::getLinks return multiple add links per activity type
authorcolemanw <coleman@civicrm.org>
Mon, 27 Nov 2023 14:37:22 +0000 (09:37 -0500)
committercolemanw <coleman@civicrm.org>
Sun, 24 Dec 2023 00:29:21 +0000 (19:29 -0500)
CRM/Activity/Form/ActivityLinks.php
Civi/API/Event/RespondEvent.php
Civi/Api4/Service/Links/ActivityLinksProvider.php [new file with mode: 0644]

index 238758614a1f01b0af880d0f39846bef489ce5c7..c8b351556a02cfa34a781150eaf5945a0b8a94cf 100644 (file)
@@ -28,73 +28,20 @@ class CRM_Activity_Form_ActivityLinks extends CRM_Core_Form {
    * @param self $self
    */
   public static function commonBuildQuickForm($self) {
-    $contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $self);
-    if (!$contactId) {
-      $contactId = CRM_Utils_Request::retrieve('cid', 'Positive');
-    }
-    $urlParams = "action=add&reset=1&cid={$contactId}&selectedChild=activity&atype=";
-
-    $allTypes = CRM_Utils_Array::value('values', civicrm_api3('OptionValue', 'get', [
-      'option_group_id' => 'activity_type',
-      'is_active' => 1,
-      'options' => ['limit' => 0, 'sort' => 'weight'],
-    ]));
+    $contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $self) ?: CRM_Utils_Request::retrieve('cid', 'Positive');
 
     $activityTypes = [];
 
-    foreach ($allTypes as $act) {
-      $url = 'civicrm/activity/add';
-      if ($act['name'] === 'Email') {
-        if (!CRM_Utils_Mail::validOutBoundMail() || !$contactId) {
-          continue;
-        }
-        [, $email, $doNotEmail, , $isDeceased] = CRM_Contact_BAO_Contact::getContactDetails($contactId);
-        if (!$doNotEmail && $email && !$isDeceased) {
-          $url = 'civicrm/activity/email/add';
-          $act['label'] = ts('Send an Email');
-        }
-        else {
-          continue;
-        }
-      }
-      elseif ($act['name'] === 'SMS') {
-        if (!$contactId || !CRM_SMS_BAO_Provider::activeProviderCount() || !CRM_Core_Permission::check('send SMS')) {
-          continue;
-        }
-        // Check for existence of a mobile phone and ! do not SMS privacy setting
-        try {
-          $phone = civicrm_api3('Phone', 'getsingle', [
-            'contact_id' => $contactId,
-            'phone_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Mobile'),
-            'return' => ['phone', 'contact_id'],
-            'options' => ['limit' => 1, 'sort' => "is_primary DESC"],
-            'api.Contact.getsingle' => [
-              'id' => '$value.contact_id',
-              'return' => 'do_not_sms',
-            ],
-          ]);
-        }
-        catch (CRM_Core_Exception $e) {
-          continue;
-        }
-        if (!$phone['api.Contact.getsingle']['do_not_sms'] && $phone['phone']) {
-          $url = 'civicrm/activity/sms/add';
-        }
-        else {
-          continue;
-        }
-      }
-      elseif ($act['name'] == 'Print PDF Letter') {
-        $url = 'civicrm/activity/pdf/add';
-      }
-      elseif (!empty($act['filter']) || (!empty($act['component_id']) && $act['component_id'] != '1')) {
-        continue;
-      }
-      $act['url'] = CRM_Utils_System::url($url,
-        "{$urlParams}{$act['value']}", FALSE, NULL, FALSE
-      );
-      $act += ['icon' => 'fa-plus-square-o'];
-      $activityTypes[$act['value']] = $act;
+    $activityLinks = \Civi\Api4\Activity::getLinks()
+      ->addValue('target_contact_id', $contactId)
+      ->addWhere('ui_action', '=', 'add')
+      ->execute();
+    foreach ($activityLinks as $activityLink) {
+      $activityTypes[] = [
+        'label' => $activityLink['text'],
+        'icon' => $activityLink['icon'],
+        'url' => CRM_Utils_System::url($activityLink['path'], NULL, FALSE, NULL, FALSE),
+      ];
     }
 
     $self->assign('activityTypes', $activityTypes);
index 462cc6edab22fc358ab79d0e6e3c4d23e93a3507..89ee03fbc2dd964b7dbbce3b84d0c7b5f93a0abd 100644 (file)
@@ -21,7 +21,7 @@ namespace Civi\API\Event;
  */
 class RespondEvent extends Event {
   /**
-   * @var mixed
+   * @var \Civi\Api4\Generic\Result|mixed
    */
   private $response;
 
@@ -30,7 +30,7 @@ class RespondEvent extends Event {
    *   The API provider responsible for executing the request.
    * @param array $apiRequest
    *   The full description of the API request.
-   * @param mixed $response
+   * @param \Civi\Api4\Generic\Result|mixed $response
    *   The response to return to the client.
    * @param \Civi\API\Kernel $apiKernel
    *   The kernel which fired the event.
@@ -41,7 +41,7 @@ class RespondEvent extends Event {
   }
 
   /**
-   * @return mixed
+   * @return \Civi\Api4\Generic\Result|mixed
    */
   public function getResponse() {
     return $this->response;
diff --git a/Civi/Api4/Service/Links/ActivityLinksProvider.php b/Civi/Api4/Service/Links/ActivityLinksProvider.php
new file mode 100644 (file)
index 0000000..7b95e01
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Service\Links;
+
+use Civi\API\Event\RespondEvent;
+use Civi\Api4\OptionValue;
+
+/**
+ * @service
+ * @internal
+ */
+class ActivityLinksProvider extends \Civi\Core\Service\AutoSubscriber {
+  use LinksProviderTrait;
+
+  public static function getSubscribedEvents(): array {
+    return [
+      'civi.api.respond' => 'alterActivityLinksResult',
+    ];
+  }
+
+  public static function alterActivityLinksResult(RespondEvent $e): void {
+    $request = $e->getApiRequest();
+    if ($request['version'] == 4 && $request->getEntityName() === 'Activity' && is_a($request, '\Civi\Api4\Action\GetLinks')) {
+      $links = (array) $e->getResponse();
+      $addLinkIndex = self::getActionIndex($links, 'add');
+      $editLinkIndex = self::getActionIndex($links, 'update');
+      $deleteLinkIndex = self::getActionIndex($links, 'delete');
+      // Expand the "add" link to multiple activity types if it exists (otherwise the WHERE clause excluded it and we should too)
+      if ($request->getExpandMultiple() && isset($addLinkIndex)) {
+        // Expanding the "add" link requires a value for target_contact.
+        // This might come back from SearchKit in a couple different ways,
+        // either an implicit join on 'target_contact_id' or as an explicit join.
+        $targetContactId = $request->getValue('target_contact_id');
+        foreach ($request->getValues() as $valueKey => $value) {
+          if (!$targetContactId && is_numeric($value) && preg_match('/^Activity_ActivityContact_Contact_\d\d\.id$/', $valueKey)) {
+            $targetContactId = $value;
+          }
+        }
+        if ($targetContactId) {
+          // Ensure links contain exactly the return values requested in the SELECT clause
+          $addLinks = self::getActivityTypeAddLinks($targetContactId, $request->getCheckPermissions());
+          foreach ($addLinks as &$addLink) {
+            $addLink += $links[$addLinkIndex];
+            $addLink = array_intersect_key($addLink, $links[$addLinkIndex]);
+          }
+          // Replace the one generic "add" link with multiple per-activity-type links
+          array_splice($links, $addLinkIndex, 1, $addLinks);
+        }
+      }
+      // With an activity type provided, alter path of edit links appropriately
+      $activityType = $request->getValue('activity_type_id:name');
+      $activityId = $request->getValue('id');
+      // Lookup activity type from id
+      if (!$activityType && $activityId) {
+        $activityTypeId = \CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, 'activity_type_id');
+        $activityType = \CRM_Core_PseudoConstant::getName('CRM_Activity_DAO_Activity', 'activity_type_id', $activityTypeId);
+      }
+      if ($activityType) {
+        $viewOnlyTypes = \CRM_Activity_BAO_Activity::getViewOnlyActivityTypeIDs($request->getCheckPermissions());
+        // Remove edit & delete links for "view only" types
+        if (isset($viewOnlyTypes[$activityType])) {
+          unset($links[$editLinkIndex], $links[$deleteLinkIndex]);
+        }
+      }
+      $e->getResponse()->exchangeArray(array_values($links));
+    }
+  }
+
+  private static function getActivityTypeAddLinks($contactId, $checkPermissions): array {
+    $addLinks = [];
+    $activityTypeQuery = OptionValue::get(FALSE)
+      ->addSelect('name', 'label', 'icon', 'value')
+      ->addWhere('option_group_id:name', '=', 'activity_type')
+      ->addWhere('is_active', '=', TRUE)
+      ->addWhere('filter', 'IS EMPTY')
+      ->addWhere('component_id', 'IS NULL')
+      ->addOrderBy('weight');
+
+    // TODO: Code block was moved from CRM_Activity_Form_ActivityLinks and could use further cleanup
+    $urlParams = "action=add&reset=1&cid={$contactId}&selectedChild=activity&atype=";
+    foreach ($activityTypeQuery->execute() as $act) {
+      $url = 'civicrm/activity/add';
+      if ($act['name'] === 'Email') {
+        if (!\CRM_Utils_Mail::validOutBoundMail()) {
+          continue;
+        }
+        [, $email, $doNotEmail, , $isDeceased] = \CRM_Contact_BAO_Contact::getContactDetails($contactId);
+        if (!$doNotEmail && $email && !$isDeceased) {
+          $url = 'civicrm/activity/email/add';
+          $act['label'] = ts('Send an Email');
+        }
+        else {
+          continue;
+        }
+      }
+      elseif ($act['name'] === 'SMS') {
+        if (!\CRM_SMS_BAO_Provider::activeProviderCount() ||
+          ($checkPermissions && !\CRM_Core_Permission::check('send SMS'))
+        ) {
+          continue;
+        }
+        // Check for existence of a mobile phone and ! do not SMS privacy setting
+        try {
+          $phone = civicrm_api3('Phone', 'getsingle', [
+            'contact_id' => $contactId,
+            'phone_type_id' => \CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Mobile'),
+            'return' => ['phone', 'contact_id'],
+            'options' => ['limit' => 1, 'sort' => "is_primary DESC"],
+            'api.Contact.getsingle' => [
+              'id' => '$value.contact_id',
+              'return' => 'do_not_sms',
+            ],
+          ]);
+        }
+        catch (\CRM_Core_Exception $e) {
+          continue;
+        }
+        if (!$phone['api.Contact.getsingle']['do_not_sms'] && $phone['phone']) {
+          $url = 'civicrm/activity/sms/add';
+        }
+        else {
+          continue;
+        }
+      }
+      elseif ($act['name'] == 'Print PDF Letter') {
+        $url = 'civicrm/activity/pdf/add';
+      }
+
+      $act['icon'] = $act['icon'] ?? 'fa-plus-square-o';
+      $act['path'] = "$url?$urlParams{$act['value']}";
+      $act['text'] = $act['label'];
+      $addLinks[] = $act;
+    }
+
+    return $addLinks;
+  }
+
+}