Merge pull request #13298 from colemanw/extCompat
[civicrm-core.git] / CRM / Activity / BAO / Activity.php
index 1b69bee337ab974fe3f8194f4a85029aae7ad6a9..478873183dd95ecfef95c5c3c172103f98895f0b 100644 (file)
@@ -1118,21 +1118,49 @@ ORDER BY    fixed_sort_order
   }
 
   /**
-   * Get the component id and name if those are enabled and allowed.
+   * @inheritDoc
+   */
+  public function addSelectWhereClause() {
+    $clauses = parent::addSelectWhereClause();
+    if (!CRM_Core_Permission::check('view all activities')) {
+      $permittedActivityTypeIDs = self::getPermittedActivityTypes();
+      if (empty($permittedActivityTypeIDs)) {
+        // This just prevents a mysql fail if they have no access - should be extremely edge case.
+        $permittedActivityTypeIDs = [0];
+      }
+      $clauses['activity_type_id'] = ('IN (' . implode(', ', $permittedActivityTypeIDs) . ')');
+    }
+    return $clauses;
+  }
+
+  /**
+   * Get an array of components that are accessible by the currenct user.
    *
-   * Checks whether logged in user has permission.
-   * To decide whether we are going to include
-   * component related activities with core activity retrieve process.
-   * (what did that just mean?)
+   * This means checking if they are enabled and if the user has appropriate permission.
    *
-   * @return array
+   * For most components the permission is access component (e.g 'access CiviContribute').
+   * Exceptions as CiviCampaign (administer CiviCampaign) and CiviCase
+   * (accesses a case function which enforces edit all cases or edit my cases. Case
+   * permissions are also handled on a per activity basis).
+   *
+   * Checks whether logged in user has permission to the component.
+   *
+   * @param bool $excludeComponentHandledActivities
+   *   Should we exclude components whose display is handled in the components.
+   *   In practice this means should we include CiviCase in the results. Presumbaly
+   *   at the time it was decided case activities should be shown in the case framework and
+   *   that this concept might be extended later. In practice most places that
+   *   call this then re-add CiviCase in some way so it's all a bit... odd.
+   *
+   * @return array Array of component id and name.
    *   Array of component id and name.
    */
-  public static function activityComponents() {
+  public static function activityComponents($excludeComponentHandledActivities = TRUE) {
     $components = array();
     $compInfo = CRM_Core_Component::getEnabledComponents();
     foreach ($compInfo as $compObj) {
-      if (!empty($compObj->info['showActivitiesInCore'])) {
+      $includeComponent = !$excludeComponentHandledActivities || !empty($compObj->info['showActivitiesInCore']);
+      if ($includeComponent) {
         if ($compObj->info['name'] == 'CiviCampaign') {
           $componentPermission = "administer {$compObj->name}";
         }
@@ -2397,9 +2425,7 @@ AND cl.modified_id  = c.id
       $followupParams['target_contact_id'] = $params['target_contact_id'];
     }
 
-    $followupParams['activity_date_time'] = CRM_Utils_Date::processDate($params['followup_date'],
-      $params['followup_date_time']
-    );
+    $followupParams['activity_date_time'] = $params['followup_date'];
     $followupActivity = self::create($followupParams);
 
     return $followupActivity;
@@ -2681,15 +2707,17 @@ AND cl.modified_id  = c.id
       return FALSE;
     }
 
+    if (!self::hasPermissionForActivityType($activity->activity_type_id)) {
+      // this check is redundant for api access / anything that calls the selectWhereClause
+      // to determine ACLs.
+      return FALSE;
+    }
     // Return early when it is case activity.
     // Check for CiviCase related permission.
     if (CRM_Case_BAO_Case::isCaseActivity($activityId)) {
       return self::isContactPermittedAccessToCaseActivity($activityId, $action, $activity->activity_type_id);
     }
 
-    // Component related permissions.
-    $allow = self::hasPermissionForActivityType($activity->activity_type_id);
-
     // Check for this permission related to contact.
     $permission = CRM_Core_Permission::VIEW;
     if ($action == CRM_Core_Action::UPDATE) {
@@ -2702,43 +2730,42 @@ AND cl.modified_id  = c.id
     $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
 
     // Check for source contact.
-    if ($allow) {
-      $sourceContactId = self::getActivityContact($activity->id, $sourceID);
-      // Account for possibility of activity not having a source contact (as it may have been deleted).
-      $allow = $sourceContactId ? CRM_Contact_BAO_Contact_Permission::allow($sourceContactId, $permission) : TRUE;
+    $sourceContactId = self::getActivityContact($activity->id, $sourceID);
+    // Account for possibility of activity not having a source contact (as it may have been deleted).
+    $allow = $sourceContactId ? CRM_Contact_BAO_Contact_Permission::allow($sourceContactId, $permission) : TRUE;
+    if (!$allow) {
+      return FALSE;
     }
 
     // Check for target and assignee contacts.
-    if ($allow) {
-      // First check for supper permission.
-      $supPermission = 'view all contacts';
-      if ($action == CRM_Core_Action::UPDATE) {
-        $supPermission = 'edit all contacts';
-      }
-      $allow = CRM_Core_Permission::check($supPermission);
-
-      // User might have sufficient permission, through acls.
-      if (!$allow) {
-        $allow = TRUE;
-        // Get the target contacts.
-        $targetContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $targetID);
-        foreach ($targetContacts as $cnt => $contactId) {
+    // First check for supper permission.
+    $supPermission = 'view all contacts';
+    if ($action == CRM_Core_Action::UPDATE) {
+      $supPermission = 'edit all contacts';
+    }
+    $allow = CRM_Core_Permission::check($supPermission);
+
+    // User might have sufficient permission, through acls.
+    if (!$allow) {
+      $allow = TRUE;
+      // Get the target contacts.
+      $targetContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $targetID);
+      foreach ($targetContacts as $cnt => $contactId) {
+        if (!CRM_Contact_BAO_Contact_Permission::allow($contactId, $permission)) {
+          $allow = FALSE;
+          break;
+        }
+      }
+
+      // Get the assignee contacts.
+      if ($allow) {
+        $assigneeContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $assigneeID);
+        foreach ($assigneeContacts as $cnt => $contactId) {
           if (!CRM_Contact_BAO_Contact_Permission::allow($contactId, $permission)) {
             $allow = FALSE;
             break;
           }
         }
-
-        // Get the assignee contacts.
-        if ($allow) {
-          $assigneeContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $assigneeID);
-          foreach ($assigneeContacts as $cnt => $contactId) {
-            if (!CRM_Contact_BAO_Contact_Permission::allow($contactId, $permission)) {
-              $allow = FALSE;
-              break;
-            }
-          }
-        }
       }
     }
 
@@ -2755,79 +2782,58 @@ AND cl.modified_id  = c.id
    * @return bool
    */
   protected static function isContactPermittedAccessToCaseActivity($activityId, $action, $activityTypeID) {
-    $allow = FALSE;
-    foreach (['administer CiviCase', 'access my cases and activities', 'access all cases and activities'] as $per) {
-      if (CRM_Core_Permission::check($per)) {
-        $allow = TRUE;
-        break;
-      }
-    }
-
-    // Check for case specific permissions.
-    if ($allow) {
-      $oper = 'view';
-      if ($action == CRM_Core_Action::UPDATE) {
-        $oper = 'edit';
-      }
-      $allow = CRM_Case_BAO_Case::checkPermission($activityId,
-        $oper,
-        $activityTypeID
-      );
+    $oper = 'view';
+    if ($action == CRM_Core_Action::UPDATE) {
+      $oper = 'edit';
     }
+    $allow = CRM_Case_BAO_Case::checkPermission($activityId,
+      $oper,
+      $activityTypeID
+    );
 
     return $allow;
   }
 
   /**
+   * Check if the logged in user has permission to access the given activity type.
+   *
    * @param int $activityTypeID
+   *
    * @return bool
    */
   protected static function hasPermissionForActivityType($activityTypeID) {
-    $compPermissions = [
-      'CiviCase' => [
-        'administer CiviCase',
-        'access my cases and activities',
-        'access all cases and activities',
-      ],
-      'CiviMail' => ['access CiviMail'],
-      'CiviEvent' => ['access CiviEvent'],
-      'CiviGrant' => ['access CiviGrant'],
-      'CiviPledge' => ['access CiviPledge'],
-      'CiviMember' => ['access CiviMember'],
-      'CiviReport' => ['access CiviReport'],
-      'CiviContribute' => ['access CiviContribute'],
-      'CiviCampaign' => ['administer CiviCampaign'],
-    ];
-
-    // First check the component permission.
-    $sql = "
-    SELECT  component_id
-      FROM  civicrm_option_value val
-INNER JOIN  civicrm_option_group grp ON ( grp.id = val.option_group_id AND grp.name = %1 )
-     WHERE  val.value = %2";
-    $params = [
-      1 => ['activity_type', 'String'],
-      2 => [$activityTypeID, 'Integer'],
-    ];
-    $componentId = CRM_Core_DAO::singleValueQuery($sql, $params);
-
-    if ($componentId) {
-      $componentName = CRM_Core_Component::getComponentName($componentId);
-      $compPermission = CRM_Utils_Array::value($componentName, $compPermissions);
+    $permittedActivityTypes = self::getPermittedActivityTypes();
+    return isset($permittedActivityTypes[$activityTypeID]);
+  }
 
-      // Here we are interesting in any single permission.
-      if (is_array($compPermission)) {
-        foreach ($compPermission as $per) {
-          if (CRM_Core_Permission::check($per)) {
-            return TRUE;
-          }
-        }
-      }
-    }
-    else {
-      return TRUE;
-    }
-    return FALSE;
+  /**
+   * Get the activity types the user is permitted to access.
+   *
+   * The types are filtered by the components they have access to. ie. a user
+   * with access CiviContribute but not CiviMember will see contribution related
+   * activities and activities with no component (e.g meetings) but not member related ones.
+   *
+   * @return array
+   */
+  protected static function getPermittedActivityTypes() {
+    $userID = (int) CRM_Core_Session::getLoggedInContactID();
+    if (!isset(Civi::$statics[__CLASS__]['permitted_activity_types'][$userID])) {
+      $permittedActivityTypes = [];
+      $components = self::activityComponents(FALSE);
+      $componentClause = empty($components) ? '' : (' OR component_id IN (' . implode(', ', array_keys($components)) . ')');
+
+      $types = CRM_Core_DAO::executeQuery(
+        "
+    SELECT  option_value.value activity_type_id
+      FROM  civicrm_option_value option_value
+INNER JOIN  civicrm_option_group grp ON (grp.id = option_group_id AND grp.name = 'activity_type')
+     WHERE  component_id IS NULL $componentClause")->fetchAll();
+      foreach ($types as $type) {
+        $permittedActivityTypes[$type['activity_type_id']] = (int) $type['activity_type_id'];
+      }
+      Civi::$statics[__CLASS__]['permitted_activity_types'][$userID] = $permittedActivityTypes;
+    }
+    return Civi::$statics[__CLASS__]['permitted_activity_types'][$userID];
   }
 
   /**
@@ -3121,7 +3127,7 @@ INNER JOIN  civicrm_option_group grp ON ( grp.id = val.option_group_id AND grp.n
             $fileValues = CRM_Core_BAO_File::path($value, $params['activityID']);
             $customParams["custom_{$key}_-1"] = array(
               'name' => $fileValues[0],
-              'path' => $fileValues[1],
+              'type' => $fileValues[1],
             );
           }
           else {