title: Zend Acl - is it possible for an assertion to allow access to a privilege when the acl rules return false?
body: <p>By default, returning false in an assertion denies access even when the acl rules allow it. What I want to do is the other way round - i.e. return true in an assertion to allow access when the acl rules deny it.</p>
  
  <p>For example:</p>
  
  <pre><code>class TestAssert implements Zend_Acl_Assert_Interface
  {
      public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $role = null, Zend_Acl_Resource_Interface $resource = null,
                             $privilege = null)
      {
          return true;
      }
  }
  
  $acl = new Zend_Acl;
  $acl-&gt;addResource('page');
  $acl-&gt;addRole('user');
  
  $acl-&gt;allow('user', 'page', 'read', new TestAssert);
  $acl-&gt;isAllowed('user', 'page', 'write');
  </code></pre>
  
  <p>The above isAllowed() will return false because the privilege I am asking about is not allowed in the acl rules, therefore it isn't getting as far as the assertion.</p>
  
  <p>Is what I want possible?</p>
  
  <p>The reason I want to do this is that a user may have certain privileges for all pages and then a different set of privileges for pages they have created - when querying for a privilege I want to be able to tell if the page is theirs and if it is, apply the rules for "own-page" rather than "page". Obviously in the real app, user and page objects are passed instead of strings.</p>
  
  <p><strong>UPDATE:</strong> I have managed to do what I want by subclassing Zend_Acl.</p>
  
  <p>This class allows callbacks to be added for specific role and resource combinations. When isAllowed() is invoked, the callback, if it exists, is run. It is passed in the same parameters as an assertion (as well as the result of parent::isAllowed()). The return value of this callback is return from isAllowed(), unless it is null, in which case the normal return value is used.</p>
  
  <pre><code>class Acl extends Zend_Acl
  {
      protected $_callbacks = array();
  
      /**
       * Takes into account the callback to determine whether the role is allowed access
       * 
       * @param Zend_Acl_Role_Interface $role
       * @param Zend_Acl_Resource_Interface $resource
       * @param string $privilege
       * @return bool
       */
      public function isAllowed($role = null, $resource = null, $privilege = null)
      {
          $result = parent::isAllowed($role, $resource, $privilege);
          $role = $this-&gt;getRole($role)-&gt;getRoleId();
          $resource = $this-&gt;get($resource)-&gt;getResourceId();
  
          if(isset($this-&gt;_callbacks[$role][$resource]))
          {           
              $callbackResult = call_user_func($this-&gt;_callbacks[$role][$resource], $this, $role, $resource, $privilege, $result);
          }
  
          return isset($callbackResult) ? $callbackResult : $result;
      }
  
      /**
       * Add a callback for a specific role and resource combination.
       * If the callback returns a value, this is used as the return value for isAllowed().
       * Otherwise, if the callback returns null, the normal result of isAllowed() will be used.
       * 
       * @param Zend_Acl_Role_Interface $role
       * @param Zend_Acl_Resource_Interface $resource
       * @param callback $callback 
       */
      public function addCallback($role, $resource, $callback)
      {
          $role = $this-&gt;getRole($role)-&gt;getRoleId();
          $resource = $this-&gt;get($resource)-&gt;getResourceId();
          $this-&gt;_callbacks[$role][$resource] = $callback;
      }
  }
  </code></pre>
  
  <p>Usage:</p>
  
  <pre><code>$acl = new Acl;
  $acl-&gt;addResource('page');
  $acl-&gt;addRole('user');
  
  $acl-&gt;addCallback('user', 'page', function($acl, $role, $resource, $privilege, $result)
  {
      return true;
  });
  
  
  $acl-&gt;allow('user', 'page', array('read'));
  $acl-&gt;isAllowed('user', 'page', 'edit'); //returns true even though 'edit' is not allowed.
  </code></pre>