Neseniai pradėjau dirbti su karkasu (angl. framework) ir žinoma prireikė į projektą įdiegti narių autorizavimo ir autentifikavimo sistemas.Kadangi CakePHP 1.2 versija yra vis dar ‘development’ stadijoje ir joje vis dar yra klaidų, įvaldyti tam skirtus įrankius prireikė nemažai laiko. Šiame straipsnyje pasidalinsiu mintimis, kaip aš išspręndžiau šią užduotį.

1.2 turi du integruotus komponentus - ir . Deja, abiejų taip lanksčiai kaip norėjau pritaikyti man nepavyko, tad komponento pagalbos teko atsisakyti.

Prieš rašant šį straipsnį 1.2 dar turėjo neištaisytą ‘bug`ą’ komponente, tačiau dabar jis ištaisytas, tad ir kodą man teko šiek tiek pakoreguoti, o jo dar neišbandžiau, tad jei pasitaikys viena kita klaida - nepykite ir pataisykite. Neabejoju, jog mano sprendimas nėra pats tobuliausias ir galima padaryti geriau, tad lauksiu komentarų ir pasiūlymų.

Na, o dabar grįžtu prie temos.
Iš anksto perspėju, jog pasirinkau ‘actions’ sprendimo būdą, tad jeigu kas nors nori ‘crud’ tipo teisių tikrinimo, pateikti pavyzdžiai nelabai tiks (tiks, tačiau reikės papildyti kodą).
‘Actions’ sprendimo būdas - teisių ribojimas pagal kontrolerių veiksmus/parametrus ir t.t.
‘CRUD’ sprendimo būdas - teisių išskirstymas į keturis veiksmus (Create, Read, Update, Delete) pagal modelius.
(Access Control Lists) - tai būdas valdyti aplikacijos teises. struktūrą sudaro dvi dalys - ARO ir ACO. ARO (Access Request Object) - objektas, kuriam apribojame laisves kreiptis į ACO (Access Control Object). Kad paprasčiau būtų suprasti, mūsų atveju: ARO - vartotojai/grupės, ACO - Controllers (kontroleriai), Actions (veiksmai), Params (parametrai).

veikia medžio principu.
Tarkime turime šias grupes: Public (viešos teisės), Admin (administratorių grupė), User (narių grupė);
vartotojus: admin1 (jis priklauso grupei Admin), user2 (jis priklauso grupei User);
Kontrolerius: posts (veiksmai: index, fullpost, edit, add), users (veiksmai: index, register, login, edit), admins (veiksmai: *), na ir žinoma nepamirštame pagrindinės direktorijos (/), ją vadinsime ROOT.

AROs medis atrodytų taip:

Public
|- Admin
|- admin1
|- User
|- user2

ACOs medis:

/ ROOT
|- posts
|- index
|- fullpost
|- edit
|- add
|- users
|- index
|- register
|- login
|- edit
|- admins
|- *...

Manau galima sugalvoti ir geresnį medžio išdėstymą, bet dabar pasilikim prie šio.
Principas: Public vartotojas nieko negali, jeigu nenurodyta kitaip. Jeigu nenustatytos Admin grupės teisės, ir teisės atskiram vartotojui, tai visi vartotojai toje grupėje turės Public nustatytas teises, kitu atveju turės jiems konkrečiai leidžiamus veiksmus.

Aš noriu suteikti grupėms tokias teises:
posts/index ir posts/fullpost gali matyti visi.
posts/edit ir posts/add gali pasiekti tik vartotojai priklausantys grupėms User ir Admin, kitką gali matyti Public vartotojai.
users/index ir users/edit tik User ir Admin, kitką Public.
admins/* gali atlikti tik administratoriai.

Prieš suteikiant teises lankytojui, mes turime jį identifikuoti. Mums reikės autentifikacijos skripto.
Pirmiausia turime susikurti grupių lentelę:

CREATE TABLE `groups` (
  `id` smallint(5) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(50) collate utf8_unicode_ci DEFAULT NULL,
  `parent_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=4;
 
 
INSERT INTO `groups` (`id`, `name`, `parent_id`) VALUES 
(1, 'Public', 0),
(2, 'Admin', 1),
(3, 'User', 1);

Modelio failas:

 <?php
class Group extends AppModel {
        var $name = 'Group';
        var $actsAs = array('Acl'=>'requester');        
     var $hasMany = array('User' =>
                          array('className'     => 'User',
                                'conditions'    => '',
                                'order'         => '',
                                'limit'         => '',
                                'foreignKey'    => 'group_id',
                                'dependent'     => false,
                                'exclusive'     => false,
                                'finderQuery'   => ''
                          )
                   );
 
        function parentNode(){
                if (!$this->id) {
                return null;
                }
                $data = $this->read();
 
                if (!$data['Group']['parent_id']){
                        return null;
                } else {
                        return $data['Group']['parent_id'];
                }
        }
}
?>

Kam skirtas actsAs, hasMany ir parentNode(), bei vėliau būsiantys standartiniai metodai/kintamieji aprašyti API (http://api..org/1.2/) ir dokumentacijoje (http://manual..org/).

Toliau mums reikia narių lentelės - users:

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(16) collate utf8_unicode_ci NOT NULL,
  `password` varchar(40) collate utf8_unicode_ci NOT NULL,
  `email` varchar(255) collate utf8_unicode_ci NOT NULL,
  `active` tinyint(1) NOT NULL DEFAULT '1',
  `group_id` tinyint(10) NOT NULL DEFAULT '3',
  `modified` datetime NOT NULL,
  `created` datetime NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `email` (`email`),
  UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=3 ;
 
 
INSERT INTO `fp_users` (`id`, `username`, `password`, `email`, `active`, `group_id`, `modified`, `created`) VALUES 
(1, 'admin1', '0926293e7126f2884d6de811567c1231a01d749c', 'admin1@example.com', 1, 2, '0000-00-00 00:00:00', '0000-00-00 00:00:00'),
(2, 'user2', '0926293e7126f2884d6de811567c1231a01d749c', 'user2@example.com', 1, 3, '0000-00-00 00:00:00', '0000-00-00 00:00:00');

Vartotojų slaptažodžiai yra užkoduoti sha1 formatu. Abiejų aukščiau esančių vartotojų slaptažodis yra: 1 (mėgstu lengvus slaptažodžius testavimo reikalamas :) ).
User modelis (/app/models/user.):

 <?php  
class User extends AppModel 
{
 var $name = 'User';
 var $actsAs = array( 'Acl'=>'requester' );
 var $belongsTo = array(
      'Group' =>
      array('className'  => 'Group',
       'conditions' => '',
       'order'      => '',
       'foreignKey' => 'group_id'
      )
     );
 
 var $validate = array(
      'email' => array(
       'unique' => array(
           'rule' => array('isUnique', 'email')
          ), 
       'format' => array(
           'rule' => array('email')
          ),
      ), 
      'username' => array(
       'unique' => array(
           'rule' => array('isUnique', 'username')
          ),
       'format' => array( 
           'rule' => array('alphaNumeric')
          ), 
       'length' => array( 
           'rule' => array('between', 2, 16)
          )
      ),
      'terms' => array(
       'required' => '/1/'
      )
     );
  var $p4ss = null;
 function beforeValidate()
 {
  $password = $this->generatePassword();
  $this->data[$this->name]["password"] = Security::hash(CAKE_SESSION_STRING . $password);
  $this->p4ss = $password;
  return true;
 }
 function afterSave()
 {
  $data = $this->read();
  $this->query('UPDATE `aros` SET `alias` = \'' . $data['User']['username'] . '\' WHERE `model` = \'User\' AND `foreign_key` = ' . $data['User']['id']);
  return true;
 }
 function generatePassword($number = 6)
 {
  $number = ( $number < 6) ? 6 : $number;
  // first character is a-z
  $pass =  chr(mt_rand(97,122));
 
  # rest are either 0-9 or a-z or A-Z
  for($k=0; $k < $number - 1; $k++)
  {
   $probab = mt_rand(1,10); 
   if($probab <= 5)   // a-z probability is 50%
    $pass .= chr(mt_rand(97,122));
   elseif($probab <= 8 && $probab > 5) // 0-9 probability is 30%
    $pass .= chr(mt_rand(48, 57));
   else
    $pass .= chr(mt_rand(65,89)); // 0-9 probability is 20%
  }
 
  return $pass;
 }
  function validateLogin($data) 
    { 
        $user = $this->find(array(
             'username' => $data['username'], 
             'password' => Security::hash(CAKE_SESSION_STRING . $data['password'])), 
             array('id', 'username')); 
        if(empty($user) == false) 
            return $user['User']; 
        return false; 
    }
    function parentNode() {
  if (!$this->id) {
   return null;
  }
 
  $data = $this->read();
 
  if (!$data['User']['group_id'])
  {
   return null;
  } else {
   return array('model' => 'Group', 'foreign_key' => $data['User']['group_id']);
  }
    }
 
}
?>

Taip pat mums reikės standartinių lentelių. Jas rasite /app/config/sql/db_acl.sql.
Dabar mums reikia įdėti pradines teises. Sukurkime kontrolerį InitAcl (/app/controllers/init_acl_controller.):

<?php
class InitAclController extends AppController
{
 var $name = 'InitAcl';
 var $components = array('Acl');
 var $uses=null;
 var $Aro=null;
 var $Aco=null;
    function setupAcl() 
  { 
    $aro = new aro(); 
    $aro->query("TRUNCATE TABLE `aros`");
    $aro->query("TRUNCATE TABLE `acos`");
    $aro->query("TRUNCATE TABLE `aros_acos`");
 
 
 
/* Add Public group */
    $aro->create(); 
    $aro->save(array( 
      'model'=>'Group', 
      'foreign_key'=>1, 
      'parent_id'=>null, 
      'alias'=>'Public')); 
 
 
 
/* Add Admin group */
    $aro->create(); 
    $aro->save(array( 
      'model'=>'Group', 
      'foreign_key'=>2, 
      'parent_id'=>1, 
      'alias'=>'Admin')); 
 
 
 
/* Add User group */
    $aro->create(); 
    $aro->save(array( 
      'model'=>'Group', 
      'foreign_key'=>3, 
      'parent_id'=>1, 
      'alias'=>'User')); 
 
/* Add admin1 user to Admin group */
    $aro->create(); 
    $aro->save(array( 
      'model'=>'User', 
      'foreign_key'=>1, 
      'parent_id'=>2, 
      'alias'=>'admin1'));      
 
 
/* Add User2 user to User group */
    $aro->create(); 
    $aro->save(array( 
      'model'=>'User', 
      'foreign_key'=>2, 
      'parent_id'=>3, 
      'alias'=>'user2'));   
 
 
      /* ACO */      
    $aco = new Aco(); 
    $aco->create(); 
    $aco->save(array( 
       'model'=>'', 
       'foreign_key'=>null, 
       'parent_id'=>null, 
       'alias'=>'ROOT')); 
 
    $aco->create();
    $aco->save(array( 
       'model'=>null, 
       'foreign_key'=>null, 
       'parent_id'=>1, 
       'alias'=>'posts')); 
 
 
    $aco->create(); 
    $aco->save(array( 
       'model'=>'MMovie', 
       'foreign_key'=>null, 
       'parent_id'=>1, 
       'alias'=>'admins'));
 
    $aco->create(); 
    $aco->save(array( 
       'model'=>'User', 
       'foreign_key'=>null, 
       'parent_id'=>1, 
       'alias'=>'users'));
 
    // Rules
   $this->Acl->deny('Public', 'ROOT');    // uzdraudziame visiems viska.
   $this->Acl->allow('Public', 'posts/index'); // leidziame visiems posts index veiksma atlitki   
 
   $this->Acl->allow('Public', 'posts/fullpost');   // leidziame visiems posts fullpost veiksma atlitki
 
   $this->Acl->allow('Public', 'users/login');    // leidziame visiems users login veiksma atlitki
 
   $this->Acl->allow('Public', 'users/register');   // leidziame visiems users register veiksma atlitki
   $this->Acl->allow('User', 'users');        // leidziame user grupei visus users veiksmus atlitki  
   $this->Acl->deny('User', 'users/login'); // uzdraudziame User grupei pasiekti login veiksma (tai reiskia jog prisijunge User grupes nariai negales eiti i login)    
 
   $this->Acl->deny('User', 'users/register'); // uzdraudziame User grupei pasiekti register veiksma
 
   $this->Acl->allow('User', 'posts');        // leidziame user grupei visus posts veiksmus atlitki
   $this->Acl->allow('Admin', 'users');   // leidziame admin grupei visus users veiksmus atlikti   
 
   $this->Acl->deny('Admin', 'users/login'); // uzdraudziame Admin grupei pasiekti login veiksma (tai reiskia jog prisijunge Admin grupes nariai negales eiti i login)    
 
   $this->Acl->deny('Admin', 'users/register'); // uzdraudziame Admin grupei pasiekti register veiksma
   $this->Acl->allow('Admin', 'posts'); // leidziame admin grupei visus posts veiksmus atlikti     
 
   $this->Acl->allow('Admin', 'admins'); // leidziame admin grupei visus admins veiksmus atlikti   
  echo "done";
  $this->autoRender = false;
   }
} 
?>

Paleiskite kontrolerį (http://jusuhost/init_acl/setupAcl/).
Dabar reikia komponento kuris atliks visą juodą darbą - identifikuos vartotoją ir patikrins jo teises.
Pavadinkim jį AclAuth. Sukurkite /app/controllers/components/acl_auth. failą:

 <?php
class AclAuthComponent extends Object {
    var $name= 'AclAuth';
    var $components= array (
        'Acl',
        'Session'
    );
    var $loginRedirect = '/';
    var $loginAction = '/login';
 
    var $user = null;
    var $aro = 'Public'; // public aro (public group)
    var $aco = 'ROOT';
    var $controller = true;
    var $allow = array('users/login', 'init_acl/setupAcl'); // leidziame tam tikras funkcijas, net jeigu jos duombazej uzdraustos
    var $acoAlias = null;
    function startup(&$controller) {
        $this->controller = &$controller;
        if (!isset($this->Acl->Aro)) {
            loadModel('Aro');
            loadModel('Aco');
            $this->Acl->Aro= new Aro; // Temporary
            $this->Acl->Aco= new Aco; // Temporary
        }
 
    $this->controller->user = $this->user($this->Session->read("Auth.user.username"));
 
 
  $aro = $this->getAro();
  $aco = $this->getAco();
 
        if (!in_array($this->acoAlias, $this->allow))
        {
         if ($this->checkAuth($aro, $aco) == true)
         {
          $this->allow();
         }
         else
         {
          $this->deny();
         }
        }
        else
        {         
   $this->allow();
        }
        $this->controller->set("user", $this->controller->user);
 
    }
    function allow()
    {
     return true;
    }
    function deny()
    {
     $this->controller->redirect($this->loginAction);
    }
 function checkAuth($aro = null, $aco = null, $action = null) {
   $action = $action ? $action : '*';
 
 
  if (!$aro)
  {
   $aro = $this->getAro();
  }
  $aco = $this->getAco($aco);
  $access = $this->Acl->check($aro, $aco, $action);
 
  if ($access === true)
  {
   return true;
  }
  return false;
 
 } 
 
    function getAro() {
        if ($this->controller->user)
        {
         $aro = $this->controller->user["User"]["username"];
        }
        if (isset($aro)) {
            if ($this->Acl->Aro->find('alias = \'' . $aro . '\'', null, null, -1)) {
                $this->aro = $aro;
            }
        }
        return $this->aro;
    }
 
     function getAco($acoAlias= null, $root = null) {
      $sep = '/';
        if (!$root) $root = $this->aco;
        if (!$acoAlias) {
            if (method_exists($this->controller, '_getAcoAlias')) {
                $acoAlias = $this->controller->_getAcoAlias();        
            } else {
                $action = $this->controller->action;
    $acoAlias= '';
 
                $acoAlias .= Inflector::underscore($this->controller->name) . $sep . $action;
 
                for($i = 0; $i < count($this->controller->params["pass"]); $i++)
                {
                 $acoAlias .= $sep . $this->controller->params["pass"][$i];
                }
 
            }
        }
        $this->acoAlias = $acoAlias;
         return $acoAlias;
    }
    function user($username = null)
    {
     if ($username && isset($this->controller->User))
     {
      $result = $this->controller->User->findByUsername($username);
      unset($result["User"]["password"]);
 
      return $result;
     }
     return null;
    }
    function __getAliasList($acoAlias, $root = 'ROOT') {
        $sep = '/';
        $results[]= $acoAlias;
        $array= explode($sep, $acoAlias);
        for ($i= 1; $i <= count($array) + 1; $i++) {
            array_pop($array);
            $results[]= implode($sep, $array);
        }
        $results[] = $root;
        return $results;
    }
}
?>

Taip pat mums reikia dar išplėsti (extend) AppController klasę (/app/app_controller.):

 
 <?php
class AppController extends Controller {
 
 var $components = array('Acl', 'Session', 'AclAuth');
 
 var $user = null;
 
 var $uses = array("User");
 var $helpers = array('Html', 'Form', 'Session');
}                         
 
?>

Dabar sukuriame narių kontrolerį - UsersController (/app/controllers/users_controller.):

<?php  
class UsersController extends AppController 
{ 
 var $name = 'Users';  
 var $aro = null;
 var $aco = null;
 var $uses = array('User', 'Group');
 
function index()
 
{
 
echo "index action";
 
exit;
 
}
 function login()
 {
  $user = $this->user;
  if ($user)
  {
   $this->autoRender = false;
   $this->redirect("/");
  }
 
  if(empty($this->data) == false) 
  { 
 
   if(($user = $this->User->validateLogin($this->data['User'])) == true) 
   { 
    $this->Session->write('Auth.user', $user); 
    $this->Session->setFlash(__("logged_in", true)); 
    $this->autoRender = false;
    $this->redirect($this->AclAuth->loginRedirect); 
   } 
   else 
   { 
    $this->Session->setFlash(__("login_error", true)); 
            } 
        }
 
  $this->set("message", $this->Session->read('Message'));
 
 }
 function logout()
 {
 
  $this->Session->del("Auth"); 
  $this->controller->user = null;
  $this->Session->setFlash(__("logged_out", true));  
 
  $this->autoRender = false;    
        $this->redirect('login');
 }
 
 function edit()
 {
  echo "edit action";
  exit;
 }
 function register() 
 { 
  if (!isset($this->user) || !$this->user)
  {
   if(!empty($this->data)) 
   {
    $this->User->data = $this->data; 
          if ($this->User->save()) 
         {
 
 
              $this->Session->setFlash(__("register_success", true) . " your pass: " . $this->User->p4ss, null, null, "smessage");
          $this->User->p4ss = null;
         }
         else
         {
              $this->Session->setFlash(__("register_unsuccess", true), null, null, "usmessage");
         }
 
    $this->set("message", $this->Session->read('Message')); 
 
       }
  }
  else 
  {
   $this->autoRender = false;
     $this->redirect("/");
  }
 }
} 
?>

Prie users kontrolerio mums žinoma reikia prisijungimo ‘view’ (app/views/users/login.ctp):

<h2><?php __("login_form_title");?></h2>    
<?php if (isset($message["flash"]["message"])) echo $message["flash"]["message"]; ?> 
    <?php echo $form->create('User', array('action' => 'login'));?> 
       <?php echo $form->label("User.username", __("username", true));?><?php echo $form->input('username', array('div' => false, 'label' => false));?> <br />
       <?php echo $form->label("User.password", __("password", true));?><?php echo $form->input('password', array('div' => false, 'label' => false));?>         <?php echo $form->submit(__('login_button', true));?> 
    <?php echo $form->end(); ?> 
 <?php echo $html->link(__("join_us", true), "/register");?>.

Na ir registracijos ‘view’ (app/views/users/register.ctp):

<h2><?php __("register_form_title");?></h2>    
<?php if (isset($message["smessage"]["message"])): ?>
 <?php echo $message["smessage"]["message"]; ?> 
<?php else: ?>
 <?php if (isset($message["usmessage"]["message"])): ?>
  <?php echo $message["usmessage"]["message"]; ?> 
 <?php endif; ?>
 <br />
 <br />
    <?php echo $form->create('User', array('action' => 'register'));?>
 
  <?php echo $form->label("User.email", __("email", true));?>
   <?php 
       echo $form->input('User.email', array(
                'div' => false, 
                'label' => false,
                'error' => array(
                    'format' => __("bad_email", true),
                    'unique' => __("email_exists", true)
                   )
               )
            );
      ?><br />
 
  <?php echo $form->label("User.username", __("username", true));?>
   <?php 
       echo $form->input('User.username', array(
                 'div' => false, 
                 'label' => false,
                 'error' => array(
                    'length' => __("username_between_2_16", true),
                    'format' => __("username_alphanumeric", true),
                    'unique' => __("username_exists", true)
                    )
                 )
            );
      ?><br />
 
  <?php echo $form->label("User.password", __("password", true));?>
   <?php 
    echo $form->input('User.password', array(
              'disabled' => 'disabled',
              'value' => '',
              'type' => 'password',
              'label' => false,
              'div' => false
             )
         );
   ?><?php __("password_will_be_shown"); ?><br />
 
 
 
  <?php echo $form->label("User.terms", __("agree_terms", true));?>
   <?php 
    echo $form->input('User.terms', array(
              'type' => 'checkbox',
              'label' => false,
              'div' => false,
              'error' => array(
                 'required' => __("must_agree_terms", true)              
                 )
              )
         );
   ?><br />
 
  <?php echo $form->submit(__('register_button', true));?> 
    <?php echo $form->end(); ?> 
<?php endif; ?>

Na ir paskutinis pakeitimas kurį reikia atlikti, tai paredaguoti cake/libs/controller/components/.. Suraskite:

for ($i = count($aroPath) - 1; $i >= 0; $i--) {

ir pakeiskime į

for ($i = 0; $i < count($aroPath); $i++) {

Šis pakeitimas reikalingas dėl kažkodėl kitaip galvojančių programuotojų. Jų ciklas tikrina teises iš visiškai kitos pusės.

Ką gi, atrodo viskas. Manau daugumai jau šiek tiek susipažinusių su klausimų neturėtų kilti. Dauguma kodo kontroleriuose/komponentuose nėra mano, tiesiog skaitydamas susirinkau reikalingus dalykus ir sulipdžiau/apkarpiau, tad už kodo netvarkingumą nuoširdžiai atsiprašau. Galbūt vėliau pateiksiu tobulesnį variantą šios sistemos.

Visą šitą reikalą pasidariau perskaitęs šiuos šaltinius:
http://manual.cakephp.org/chapter/acl
http://bakery.cakephp.org/articles/view/how-to-use-acl-in-1-2-x
http://realm3.com/articles/setting_up_users_groups_withacl_and_auth_in_cake_1.2.php
http://www.ad7six.com/MiBlog/AclPart1
http://bakery.cakephp.org/articles/view/yacca-yet-another-cake-component-for-auth
http://blog.jails.fr/cakephp/index.php?post/2007/08/15/AuthComponent-and-ACL
http://bakery.cakephp.org/articles/view/real-world-access-control
http://bakery.cakephp.org/articles/view/simple-form-authentication-in-1-2-x-x
http://manual.cakephp.org/chapter/session
http://manual.cakephp.org/chapter/validation
http://bakery.cakephp.org/articles/view/multiple-rules-of-validation-per-field-in-cakephp-1-2
http://cakebaker.42dh.com/2007/01/03/validation-with-cakephp-12/
Na ir daugybę klausimų/atsakymų Google groups: http://groups.google.com/group/cake-php/search?group=cake-php&q=ACL&qt_g=Search+this+group

Laukiu komentarų ir geresnių sprendimų nei šis.

Panašūs straipsniai


“CakePHP 1.2 ACL ir autentifikacija” komentarų: 6

  1. Artūras Šlajus

    Nu nepyk, bet straipsnis ryškiai per didelis, per daug kodo ir šiaip labai tingisi skaityti.

    Nors gal kam ir pravers…

  2. asterisk

    Taip, straipsnis labiau skirtas tiems, kurie būtent susidūrė su šia problema. Nusprendžiau pasidalinti savo “pasiekimu” nes pats prie to gaišau porą dienų ir šiek tiek atsilikau nuo grafiko :)

  3. strakalas

    irgi neperskaiciau viso, bet pasiprikabinesiu prie smulkmenos

    ar vykdant cikla

    for ($i = 0; $i

  4. asterisk

    strakalas, kažkaip nukirpo tavo komentarą :)

  5. pasigydziau

    asterisk++

  6. CakePHP 1.2 Acl ir Auth (2 dalis) » Pixel.lt

    […] tiek anksčiau pristačiau savo būdą, kaip tvarkyti autorizavimą ir autentifikaciją CakePHP karkase. Jis buvo pakankamai didelis ir netvarkingas. Taip pat keli žmonės kreipėsi, nes negalėjo jo […]

Rašyti komentarą

Jūs privalote prisijungti jeigu norite rašyti komentarą.