Answers.Body: <p>Deny ACLs have been a long-standing request, but one that no one has contributed or offered to fund.  You can <a href="https://lab.civicrm.org/dev/core/-/issues/3753" rel="nofollow noreferrer">see the open issue</a>.</p>
  <p>There's a bit of a tragedy of the commons that many developers, rather than develop the full feature, simply use a hook to meet their immediate need.</p>
  <p>To help you do that - the <code>hook_civicrm_aclGroup</code> is probably the wrong hook, though you can view its use in the <a href="https://lab.civicrm.org/extensions/multisite/-/blob/master/multisite.php#L183" rel="nofollow noreferrer">multisite extension</a>.  You probably want <code>hook_civicrm_aclWhereClause</code>.  A simpler example of this can be found in the <a href="https://github.com/circleinteractive/org.civicrm.vieweventparticipants/blob/07c3ca512c1efcd1ba4b4caf52e80a89a23c16d4/vieweventparticipants.php#L153" rel="nofollow noreferrer">View My Event Participants</a> extension, and a more complex version in the <a href="https://github.com/eileenmcnaughton/nz.co.fuzion.relatedpermissions/blob/1a040626e4fc551ede2892fc3b3f8ff5fdeb1b4d/relatedpermissions.php#L88" rel="nofollow noreferrer">Related Permissions</a> extension.</p>
  <p>Edited to add: Here's an (anonymized) code example from a client-specific extension I wrote which is simpler still, but is a more limited use case. <code>CRM_Myclientpermissions_Utils::getVipContactsIds();</code> returns a list of Contact IDs that only people with the <code>Access VIP Contacts</code> permission are allowed to see.</p>
  <pre><code>function myclientpermissions_civicrm_aclWhereClause($type, &amp;$tables, &amp;$whereTables, &amp;$contactID, &amp;$where) {
    // If the person doesn't have the &quot;Edit All Contacts&quot; permission, this is called, restricting access to the VIP groups.
    if (!$contactID) {
      return;
    }
    // If the person has the &quot;View VIP contacts&quot; permission, skip this.
    if (CRM_Core_Permission::check('access vip contacts')) {
      return;
    }
    // The calling function will apply a &quot;0&quot; for no access if the WHERE statement is empty.
    // So let's not inadvertently create a WHERE when no access is applicable.
    // That is, View Only contacts can't see anyone.
    if (!$where) {
      return;
    }
  
    $vipIds = CRM_Myclientpermissions_Utils::getVipContactsIds();
    $vips = implode(&quot;,&quot;, $vipIds);
    $where .= ($where ? ' AND ' : '') . &quot; contact_a.id NOT IN ($vips)&quot;;
  }
  </code></pre>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-01-19T15:35:36.357Z
Answers.Text: Thank you very much!
  
  Sorry for the delay in responding to this - I thought StackExchange would notify me - I must've turned off email notifications at some point in the past.
Answers.UserDisplayName: Gav Massingham
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-01-19T15:48:48.627Z
Answers.Text: Looking at View My Event Participants, that looks like an approach that would work. No idea how my GitHub search missed that, and a few other results. Thanks again.
Answers.UserDisplayName: Gav Massingham
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-12-22T18:10:38.373Z
Answers.LastActivityDate: 2022-12-22T18:29:00.843Z
Answers.LastEditDate: 2022-12-22T18:29:00.843Z
Answers.UserDisplayName: Jon G - Megaphone Tech
Body: <p>I've been battering my head against this on-and-off for months now.</p>
  <p>Why isn't there a simple way to deny access to a specific set of records?</p>
  <p>We use a <em>lot</em> of groups, so manually assigning each group to an ACL isn't feasible. The ideal solution would be to put all restricted records in a group, give everyone access to all groups, then deny them access to the restricted records group. After that, we could use the excellent Related Permissions module to control access to individual records. But there isn't a &quot;deny&quot; in Civi's ACLs.</p>
  <p>I suspect the aclGroup hook might be one I need (<a href="https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_aclGroup/" rel="nofollow noreferrer">https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_aclGroup/</a>) but the documentation, as for much of Civi, is minimal. In the example section of that page it has a link &quot;Check HRD module&quot; which is a 404. I searched github and found 1 codebase that used the hook, but in too complex a manner for me to be able to get anything from it. There are apparently NO extensions using this hook in lab.civicrm.org</p>
  <p>Any pointers about this question would be gratefully received. And not just a link to <a href="https://civicrm.org/blog/lobo/implementing-a-custom-acl-system-in-civicrm" rel="nofollow noreferrer">https://civicrm.org/blog/lobo/implementing-a-custom-acl-system-in-civicrm</a>. I've read that. It hasn't helped much.</p>
  <p>I've never known a code base that is as difficult to get into as CiviCRM - given that I've hacked on Emacs plugins, and the first patch I ever submitted was for a Drupal Views extension. I'm only a part-time programmer, so I need to be able to work my way in, not feel like I'll have to learn how most of the CiviCRM codebase works before I can do anything. So little good documentation, so many links that are broken or out-of-date, web searches that bring up results for older versions of CiviCRM. It's very frustrating, especially as it strikes me that it shouldn't be that difficult a thing to implement. I know it's open source, and I'd be happy to help with documentation, and with code, but it's too difficult to get a first foothold.</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2022-12-22T18:11:27.920Z
Comments.Text: Hi Gav - welcome, and I'm sorry to hear about your experience.  It's true that the documentation is in sorry shape - it was generally maintained during in-person sprints, which have fallen off due to COVID.  I'm going to open tickets for updating the aclGroup docs page, and also to finally get the old wiki taken offline.  Meanwhile, feel free to join us at https://chat.civicrm.org in the ~dev channel if you have follow-up questions.
Comments.UserDisplayName: Jon G - Megaphone Tech
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2022-12-22T18:16:18.650Z
Comments.Text: i don't know if this comment is relevant in this scenario but we have done a lot of our 'manage who sees what' via the Drupal cms and joining the dots by using CiviCRM Relationships.
Comments.UserDisplayName: petednz - fuzion
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2022-12-22T18:24:04.537Z
Comments.Text: I just opened this MR and issue: https://lab.civicrm.org/documentation/docs/dev/-/issues/882 and https://lab.civicrm.org/documentation/docs/dev/-/merge_requests/1047.  I hope this begins to address your frustration.
Comments.UserDisplayName: Jon G - Megaphone Tech
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2023-01-19T15:55:16.253Z
Comments.Text: Many thanks, again.
Comments.UserDisplayName: Gav Massingham
Tags: <developer><acl>
Title: Deny access to a set of records
ThreadText: # Deny access to a set of records
  
  (Asked by: username_0 on 2022-12-22)
  
  I've been battering my head against this on-and-off for months now.
  
  Why isn't there a simple way to deny access to a specific set of records?
  
  We use a *lot* of groups, so manually assigning each group to an ACL isn't feasible. The ideal solution would be to put all restricted records in a group, give everyone access to all groups, then deny them access to the restricted records group. After that, we could use the excellent Related Permissions module to control access to individual records. But there isn't a "deny" in Civi's ACLs.
  
  I suspect the aclGroup hook might be one I need (<https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_aclGroup/>) but the documentation, as for much of Civi, is minimal. In the example section of that page it has a link "Check HRD module" which is a 404. I searched github and found 1 codebase that used the hook, but in too complex a manner for me to be able to get anything from it. There are apparently NO extensions using this hook in lab.civicrm.org
  
  Any pointers about this question would be gratefully received. And not just a link to <https://civicrm.org/blog/lobo/implementing-a-custom-acl-system-in-civicrm>. I've read that. It hasn't helped much.
  
  I've never known a code base that is as difficult to get into as CiviCRM - given that I've hacked on Emacs plugins, and the first patch I ever submitted was for a Drupal Views extension. I'm only a part-time programmer, so I need to be able to work my way in, not feel like I'll have to learn how most of the CiviCRM codebase works before I can do anything. So little good documentation, so many links that are broken or out-of-date, web searches that bring up results for older versions of CiviCRM. It's very frustrating, especially as it strikes me that it shouldn't be that difficult a thing to implement. I know it's open source, and I'd be happy to help with documentation, and with code, but it's too difficult to get a first foothold.
  
  **Tags:** developer, acl
  
  > **Comments:**
  > **username_1**: Hi Gav - welcome, and I'm sorry to hear about your experience.  It's true that the documentation is in sorry shape - it was generally maintained during in-person sprints, which have fallen off due to COVID.  I'm going to open tickets for updating the aclGroup docs page, and also to finally get the old wiki taken offline.  Meanwhile, feel free to join us at https://chat.civicrm.org in the ~dev channel if you have follow-up questions.  
  > **username_2**: i don't know if this comment is relevant in this scenario but we have done a lot of our 'manage who sees what' via the Drupal cms and joining the dots by using CiviCRM Relationships.  
  > **username_1**: I just opened this MR and issue: https://lab.civicrm.org/documentation/docs/dev/-/issues/882 and https://lab.civicrm.org/documentation/docs/dev/-/merge_requests/1047.  I hope this begins to address your frustration.  
  > **username_0**: Many thanks, again.  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_1 on 2022-12-22. Score: 1 | Accepted answer
  
  Deny ACLs have been a long-standing request, but one that no one has contributed or offered to fund. You can [see the open issue](https://lab.civicrm.org/dev/core/-/issues/3753).
  
  There's a bit of a tragedy of the commons that many developers, rather than develop the full feature, simply use a hook to meet their immediate need.
  
  To help you do that - the `hook_civicrm_aclGroup` is probably the wrong hook, though you can view its use in the [multisite extension](https://lab.civicrm.org/extensions/multisite/-/blob/master/multisite.php#L183). You probably want `hook_civicrm_aclWhereClause`. A simpler example of this can be found in the [View My Event Participants](https://github.com/circleinteractive/org.civicrm.vieweventparticipants/blob/07c3ca512c1efcd1ba4b4caf52e80a89a23c16d4/vieweventparticipants.php#L153) extension, and a more complex version in the [Related Permissions](https://github.com/eileenmcnaughton/nz.co.username_2.relatedpermissions/blob/1a040626e4fc551ede2892fc3b3f8ff5fdeb1b4d/relatedpermissions.php#L88) extension.
  
  Edited to add: Here's an (anonymized) code example from a client-specific extension I wrote which is simpler still, but is a more limited use case. `CRM_Myclientpermissions_Utils::getVipContactsIds();` returns a list of Contact IDs that only people with the `Access VIP Contacts` permission are allowed to see.
  
  ```
  function myclientpermissions_civicrm_aclWhereClause($type, &$tables, &$whereTables, &$contactID, &$where) {
    // If the person doesn't have the "Edit All Contacts" permission, this is called, restricting access to the VIP groups.
    if (!$contactID) {
      return;
    }
    // If the person has the "View VIP contacts" permission, skip this.
    if (CRM_Core_Permission::check('access vip contacts')) {
      return;
    }
    // The calling function will apply a "0" for no access if the WHERE statement is empty.
    // So let's not inadvertently create a WHERE when no access is applicable.
    // That is, View Only contacts can't see anyone.
    if (!$where) {
      return;
    }
  
    $vipIds = CRM_Myclientpermissions_Utils::getVipContactsIds();
    $vips = implode(",", $vipIds);
    $where .= ($where ? ' AND ' : '') . " contact_a.id NOT IN ($vips)";
  }
  
  ```
  
  > **Comments:**
  > **username_0**: Thank you very much!  Sorry for the delay in responding to this - I thought StackExchange would notify me - I must've turned off email notifications at some point in the past.  
  > **username_0**: Looking at View My Event Participants, that looks like an approach that would work. No idea how my GitHub search missed that, and a few other results. Thanks again.  
  
  ---
  
  