Automatically load all controllers and actions into ACO tables for ACL with a CakePHP Task

This article is also available in the following languages:
By brightball
If you've spent anytime wanting to use ACL on your applications, you know how tedious it can be to manually enter your entire controller and action structure. This Task will handle finding and loading or updating all of those for you whenever you run it from the command line.

There isn't a section for Shell/Task code so I figured plugins was the place to go.
This code is setup as a task so that it can be executed by any Cake shell that includes it. All that you need to do to run it in your application is create an empty shell:

/app/vendors/shells/task_runner.php


<?php
class TaskRunnerShell extends Shell {
   var 
$tasks = array('AclControllers');
   
   function 
main() {      
      
$this->print_instructions();
   }
   
   function 
print_instructions() {
      
$this->out("\nCommands");
      
$this->hr();
      
      foreach(
$this->tasks AS $t) {
         
         
$description = isset($this->{$t}->description) ? $this->{$t}->description '';
         
$this->out($this->shell ' ' Inflector::underscore($t) . "\t$description\n");
         
      }
   }
}
?>

That's just a wrapper shell that I like to use that will run through all of it's included tasks, find the 'description' variable and list the command to execute with the description.

Next, you'll want to add the acl_controllers.php task to

/app/vendors/shells/tasks/acl_controllers.php


<?php
class AclControllersTask extends Shell {
   
   
//Used when printing instructions
   
var $description 'Automatically loads controllers and actions into ACOs';
   var 
$filter = array();
   
   function 
startup() {
      
App::import('Core','Controller');
      
App::import('Component','Acl');
      
      
$this->Acl =& new AclComponent();
      
$controller null;
      
$this->Acl->startup($controller);
      
$this->Aco =& $this->Acl->Aco;
   }
   
   function 
execute() {
      
      
$this->out('Load Controllers and Actions into ACO');
      
      
$cf =& Configure::getInstance();
      
      
$plugins Configure::listObjects('plugin');
      
      
//Find plugin controller methods
      
if(!empty($plugins)) {
         foreach(
$plugins AS $p) {
            
$this->out('Checking Plugin: ' $p);
            
$path ROOT DS APP_DIR DS 'plugins' DS strtolower($p) . DS 'controllers';
            
$this->out('Adding Plugin Path: ' $path);
            
            
$cf->controllerPaths[] = $path;
            
App::import('Controller',$p '.' $p 'AppController');
         }
      }
              
      
$controllers   Configure::listObjects('controller');
      
      
$this->out('Controllers Found: ' implode(', '$controllers));
      
      
$this->filter['methods'] = get_class_methods('Controller');
      
$this->filter['controller'] = array('App');            
      
      
$list = array();
      
      
//Find controller methods
      
foreach($controllers AS $c) {
         if(
in_array($c,$this->filter['controller'])) continue;

         
$this->out('Importing Controller: ' $c);                           
         if(!
App::import('Controller',$c)) {
            foreach(
$plugins AS $p) {
               if(
strpos($p,$c) === && App::import('Controller',$p '.' $c)) break;
            }            
         }
         
         
$list[$c] = $this->_getMethods($c 'Controller','methods');
      }
            
      
//Find ROOT node id
      
$root_id $this->Aco->field('id',array('alias' => 'ROOT'));
      
      
$this->out('');
      
$this->out('ROOT node id: ' $root_id);
      
      foreach(
$list AS $con => $acts) { //Loop through list of controllers
         
$this->out('');
         
$this->hr();
         
         
$conditions = array('alias' => $con,'parent_id' => $root_id);
         if(
$this->Aco->hasAny($conditions)) { //Check if controller is already in the table
            
$this->out('Controller Already Loaded: ' $con);
         }
         else { 
//If not create it
            
$this->Aco->create();
            if(
$this->Aco->save($conditions)) $this->out('CREATED: ' $con ' Controller');            
            else 
$this->error('Controller Create Failed',$con);            
         }
         
         
$con_id $this->Aco->field('id',$conditions);
         
//$this->out('con_id: ' . $con_id);
         
         //Get list of the controller's actions
         
$actions $this->Aco->find('list',array(
            
'conditions' => array('parent_id' => $con_id),
            
'fields' => array('alias','id')));
            
         
$this->out('Actions already loaded: ' implode(', ',$acts));   
         
//Loop through list of actions
         //print_r($acts);
         
         
foreach($acts AS $a) {            
            if(!empty(
$actions[$a])) {
               
//$this->out('Skipped: ' . $a);
            
}
            else {
               
$this->out('loading... ' $a);
               
$this->Aco->create(false);
               
               if(
$this->Aco->save(array('parent_id' => $con_id,'alias' => $a))) $this->out('CREATED: ' $con '/'  $a);
               else 
$this->error('Action Create Failed'$con '/'  $a);
            }
         }
         
      }
      
//print_r($aco);      
      //print_r($list);
   
}
   
   function 
_getMethods($className,$filter 'methods') {
      
$c_methods get_class_methods($className);
      
$c_methods array_diff($c_methods,$this->filter[$filter]);
      
$c_methods array_filter($c_methods,array($this,"_removePrivate"));
      
      return 
$c_methods;
   }
   
   function 
_removePrivate($var) {
      if(
substr($var,0,1) == '_') return false;
      else return 
true;
   }
}

The ONLY assumption that this code makes is that your ACO table has a node with an 'alias' of 'ROOT' that all of the controllers and actions will use as a parent. If you're using something other than root, the code looking for it is on line 57.

To run it, just run over to your cake/console directory and type

php cake.php task_runner acl_controllers

Comments

  • Posted 11/23/09 06:04:50 PM
    wrong thread
  • Posted 09/16/09 05:00:11 AM
    You sir, are a genius
  • Posted 08/04/09 11:39:53 AM
    Hi Barry,
    its works!! Nice, thx :)
  • Posted 08/03/09 01:25:05 PM
    Plugin controllers are not listed :(

    controllers/lists_controller.php

    Controller Class:

    <?php 
    class ListsController extends AppController {
    var 
    $name 'Lists';

    function 
    _getMethods($className,$filter 'methods') {
          
    $c_methods get_class_methods($className);
          
    $c_methods array_diff($c_methods,$this->filter[$filter]);
          
    $c_methods array_filter($c_methods,array($this,"_removePrivate"));
          
          return 
    $c_methods;
       }
       
       function 
    _removePrivate($var) {
          if(
    substr($var,0,1) == '_') return false;
          else return 
    true;
       } 
        

    function 
    _list_ctrl(){
    App::import('Core','Controller''File''Folder'); 
    $cf =& Configure::getInstance();
          
          
    $plugins Configure::listObjects('plugin');
          
          
    //Find plugin controller methods
          
    if(!empty($plugins)) {
             foreach(
    $plugins AS $p) {
                
    //$this->out('Checking Plugin: ' . $p);
                
    $path ROOT DS APP_DIR DS 'plugins' DS strtolower($p) . DS 'controllers';
                
    //$this->out('Adding Plugin Path: ' . $path);
                
                
    $cf->controllerPaths[] = $path;
                
    App::import('Controller',$p '.' $p 'AppController');
             }
          }
                  
          
    $controllers   Configure::listObjects('controller');
          
          
    //$this->out('Controllers Found: ' . implode(', ', $controllers));
          
          
    $this->filter['methods'] = get_class_methods('Controller');
          
    $this->filter['controller'] = array('App');            
          
          
    $list = array();
          
          
    //Find controller methods
          
    foreach($controllers AS $c) {
             if(
    in_array($c,$this->filter['controller'])) continue;

             
    //$this->out('Importing Controller: ' . $c);                           
             
    if(!App::import('Controller',$c)) {
                foreach(
    $plugins AS $p) {
                   if(
    strpos($p,$c) === && App::import('Controller',$p '.' $c)) break;
                }            
             }
             
             
    $list[$c] = $this->_getMethods($c 'Controller','methods');
          }
                
          return 
    $list;



    function 
    display(){
       
    $a = array();
       
    $list $this->_list_ctrl();
       
    $this->set("a",$list);
       
    }

    }
    ?>
    File: views/lists/display.ctp

    View Template:


    <?php

    pr
    ($a);

    ?>

    • Posted 08/03/09 02:15:49 PM
      It will be a few days before I get a chance to update the code base, but if you replace this line:


      $path = ROOT . DS . APP_DIR . DS . 'plugins' . DS . strtolower($p) . DS . 'controllers'; 

      with this


      $path = ROOT . DS . APP_DIR . DS . 'plugins' . DS . Inflector::underscore($p) . DS . 'controllers'; 

      That SHOULD fix it. All of the plugins that I had installed when I wrote this were single words, so it wasn't an issue. I'll update the article and post a comment when I update the main repository though.
  • Posted 02/05/09 12:22:53 PM
    There is another developer that has also developed a shell script for generating and maintaining ACL.

    His blog post about it:
    http://mark-story.com/posts/view/generate-aco-records-for-your-controllers-and-actions-with-acosyncshell
    Github to download code:
    http://github.com/markstory/story-scribbles/tree/master/cakephp/shells/
  • Posted 02/01/09 01:10:46 AM
    Just decided to mess with CakePHP today (designing a CMS). Was about to ditch CakePHP due to impossibility to easily set up ACL and then found this script. Thanks!

  • Posted 01/29/09 03:57:53 PM
    It would be nice if this cleaned up dead ACOs as well. I just noticed now that I have ACOs left from actions that have since been removed from my code. Good work, I've been using this script daily! :)
    • Posted 01/29/09 04:13:46 PM
      It would be nice if this cleaned up dead ACOs as well. I just noticed now that I have ACOs left from actions that have since been removed from my code. Good work, I've been using this script daily! :)
      I'll have to put that on the to-do list. I'll release it as soon as I get around to adding it in. I will probably need to add a "Do you want to remove this? (y/n)" just so the code doesn't break anything. Either than or add an argument like '-bring-out-your-dead'.

      Just a little leery of putting a delete into anything that I make available to anybody else.
  • Posted 01/27/09 01:05:32 PM
    Great article. It's unfortunate that ACL is so hard to use and there's not a lot documentation out there on it. This task makes life so much easier now. Thanks.
  • Posted 01/24/09 02:10:05 PM
    The lft and rght fields in the ACO table aren't being populated when this runs.
    • Posted 01/25/09 12:51:29 AM
      The lft and rght fields in the ACO table aren't being populated when this runs.
      The same thing was happening to me. I solved this problem by first commenting out the line:

      //var $uses = array('Aco'); // same as controller var $uses

      and then adding the following to the startup() function:

      App::import('Component','Acl');
      $this->Acl =& new AclComponent();
      $controller = null;
      $this->Acl->startup($controller);
      $this->Aco =& $this->Acl->Aco;

      Thanks Barry for the great article! I had something like this before myself, but this is a much cleaner version that I'm glad to use instead.
      • Posted 01/25/09 01:35:51 AM
        The same thing was happening to me. I solved this problem by first commenting out the line:

        //var $uses = array('Aco'); // same as controller var $uses

        and then adding the following to the startup() function:

        App::import('Component','Acl');
        $this->Acl =& new AclComponent();
        $controller = null;
        $this->Acl->startup($controller);
        $this->Aco =& $this->Acl->Aco;

        Thanks Barry for the great article! I had something like this before myself, but this is a much cleaner version that I'm glad to use instead.

        Nice catch! I hadn't noticed that before but I tried your updates and they worked perfectly, so I updated the article code above to use them.
  • Posted 01/23/09 09:52:22 AM
    I will try it very soon, thank you.

Comments are closed for articles over a year old