As much as a note to self as anything else, for some reason it proved hard to find out how I could use html in the auto validation messages generated by models in CakePHP. Now it’s totally arguable that the use of html is not best practice in this case, the right or wrong of that on a technical level is entirely up to you however assuming you do need/want to use html in the validation messages just make sure that on forms that are applicable you either use a custom form error trap:
Following on from a post I wrote a long time ago entitled “What does “__n()” do in cakephp?” I thought I’d follow up with an example of how to actually use the __n() with i18n in cakephp as it’s not obvious and also hard to find examples of use and or documentation.
If you’re using __(n) in version 2.0 or above Cake the documentation is here. After a brief look around I couldn’t find the 1.3 version please comment if you have the link! The documentation says this:
__n(string $singular, string $plural, integer $count, mixed $args = null)
Returns correct plural form of message identified by $singular and $plural for count $count. Some languages have more than one form for plural messages dependent on the count.
This is fine but it doesn’t result in a translation that includes the actual numbers in translated context so here as a note to self more than anything is an example of how to actually use the __n() with i18n in cakephp:
This us more a note to self than anything else, as I trawled the internet trying to find a definitive answer to this. Modified preorder tree traversal (MPTT) is an efficient way to store and query hierarchical data in a database. This article on the MySQL site explains why this is the case with a neat comparison between MPTT and a more “old school” approach which relies on a parent-child recursive relationship in the database (known as the Adjacency List Model). I wont repeat what it says but suffice to say I’ve been experimenting with MPTT for a while and like the way it does things.
I use cakephp a lot and I have recently created a hierarchy of some 120,000 items, all neatly mapped with parent-child relationships but no MPTT data. Cake’s inbuilt mechanism for converting adjacency list model to a modified preorder tree traversal (MPTT) model hierarchy was far to slow: on my PC and with this much data it was taking about a second per row, which adds up to an awfully long time to process the whole tree!
Given the need for this type of conversion I found it surprisingly difficult to find a script that the the Agency list to MPTT conversion, so I thought I’d document what I found here, and add to it if I find any further.
So, using the CakePHP naming conventions here is a script to convert Agency List to MPTT using MySQL and PHP. This is much faster than the native CakePHP model->recover() method (which rebuilds your hierarchy) which is important on big datasets obviously. This script is based on the function in this excellent article on MPTT.
//Make sure it doesn't time out
set_time_limit (0);
$con = mysql_connect("localhost","user","password");
if (!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("your_db", $con);
function rebuild_tree($parent_id, $left) {
// the right value of this node is the left value + 1
$right = $left+1;
// get all children of this node
$result = mysql_query('SELECT id FROM categories '.
'WHERE parent_id="'.$parent_id.'";');
while ($row = mysql_fetch_array($result)) {
// recursive execution of this function for each
// child of this node
// $right is the current right value, which is
// incremented by the rebuild_tree function
$right = rebuild_tree($row['id'], $right);
}
// we've got the left value, and now that we've processed
// the children of this node we also know the right value
mysql_query('UPDATE categories SET lft='.$left.', rght='.
$right.' WHERE id="'.$parent_id.'";');
// return the right value of this node + 1
return $right+1;
}
rebuild_tree('1',1);
mysql_close($con);
Using pure traditional SQL you can use the following script apparently, but I didn’t have time to make it work in MySQL and the above processed things quickly enough. The source of the script below is here http://data.bangtech.com/sql/nested_set_treeview.htm
-- Tree holds the adjacency model
CREATE TABLE Tree
(emp CHAR(10) NOT NULL,
boss CHAR(10));
INSERT INTO Tree
SELECT emp, boss FROM Personnel;
-- Stack starts empty, will holds the nested set model
CREATE TABLE Stack
(stack_top INTEGER NOT NULL,
emp CHAR(10) NOT NULL,
lft INTEGER,
rgt INTEGER);
BEGIN ATOMIC
DECLARE counter INTEGER;
DECLARE max_counter INTEGER;
DECLARE current_top INTEGER;
SET counter = 2;
SET max_counter = 2 * (SELECT COUNT(*) FROM Tree);
SET current_top = 1;
INSERT INTO Stack
SELECT 1, emp, 1, NULL
FROM Tree
WHERE boss IS NULL;
DELETE FROM Tree
WHERE boss IS NULL;
WHILE counter <= (max_counter - 2)
LOOP IF EXISTS (SELECT *
FROM Stack AS S1, Tree AS T1
WHERE S1.emp = T1.boss
AND S1.stack_top = current_top)
THEN
BEGIN -- push when top has subordinates, set lft value
INSERT INTO Stack
SELECT (current_top + 1), MIN(T1.emp), counter, NULL
FROM Stack AS S1, Tree AS T1
WHERE S1.emp = T1.boss
AND S1.stack_top = current_top;
DELETE FROM Tree
WHERE emp = (SELECT emp
FROM Stack
WHERE stack_top = current_top + 1);
SET counter = counter + 1;
SET current_top = current_top + 1;
END
ELSE
BEGIN -- pop the stack and set rgt value
UPDATE Stack
SET rgt = counter,
stack_top = -stack_top -- pops the stack
WHERE stack_top = current_top
SET counter = counter + 1;
SET current_top = current_top - 1;
END IF;
END LOOP;
END;
This morning when I uploaded my site to live I came across one of those cryptic CakePHP messages Error: The requested address “/” was not found on this serverwhich I thought I’d blog about just as a note to self. I knew I had access to everything and my Auth setup was copied from DEV where it was working so I went through my normal routine for identifying the problem.
In my case it turned out to be a number of issues (a missing file and a missing database table) but the process to identify and therefore fix the problem is:
Make sure you set up your DATABASE_CONFIG is setup correctly (database.php)
Check that your tmp folder (and all of it’s sub folders) are writable (at least chmod 666 on Linux),
While you’re there delete all cache files from allof the tmp sub folders
After that, if you’re still having “Error: The requested address “/” was not found on this server” then you’re likely to be missing a database table or an include file, so go into your core.php and set:
This is a part of my [p2p type=”id” value=”135″] series.
One of the things that I found most confusing about implementing the Auth component and ACL was their precise relationship, how they hung together, what one was and the other wasn’t or more specifically what one did and the other didn’t.
While conceptually not complicated – the Auth Component is something that identifies a user whereas the ACL component is something that takes that knowledge and says “what does this identified user have access to” – they seem (at first) so intermeshed in the “Automagic” functions of CakePHP that it can be difficult to establish what is causing what to happen.
I found this particularly so when, having not gone through the Simple-Acl-controlled-Application tutorial kindly provided by the CakePHP team, I dropped the Auth component into my components list and lo and behold I was required to authenticate against actions I had wanted protected.
It wasn’t until reading this article and doing a bit of experimenting i realised that while it was definitely “Authenticating” it was not “Checking access” so none of my ACL stuff was kicking in at all.
An example of how the two functions seem intermeshed is the allowedActions function that is specified in the beforeFilter:
$this->Auth->allowedActions = array(‘*’);
This is (as the method path shows) a function of the Auth component, but it relates to Access, in this case the actions of the controller. This is an important function to understand, this tells the AuthComponent to allow PUBLIC access to all actions, you can of course provide a specific array of actions here as well. The important thing to understand is that “public” is just a default state given to a non authenticated user, it is not a specific group you need to create and assign by default (which is the case in some authentication systems I’ve seen).
So remember that while a user can be “authenticated” that is, they are known to the system they are not necessarily being checked against their access profile, $this->Auth->allowedActions can confuse matters if you’re not careful because it can suggest that the authenticated person does or doesn’t have access to something (because they get redirected to the login page).
When working out what is tripping up a security response, use this handy guide to [p2p type=”id” value=”133″] which can tell you what is being validated against and thus if it is your dodgy ACL setup or just the default settings in the Auth component that are causing any problems.
This is a part of my [p2p type=”id” value=”135″] series.
I found a good basis for a resolution to this issue here . It wasn’t perfect for me but did a nice job of explaining a problem I had with hashed values. I have provided a modified solution below that meets my requirements more specifically (that is to allow password changes only when a password confirm is provided on edit, while requiring a password on add).
A problem I had encountered along the way was due to me not understanding the hash function, I couldn’t get the two hash values to match, this problem is highlighted in the post “Manually hashing password and password validation in CakePHP“:
Security::hash($this->data[‘User’][‘passwd’], null, true);
This line makes the password readable by the Auth component, and that in turn removes the need of creating your own. Take a look at the API reference of Security::hash() and you will see that the third parameter is very important, as it tells the hash function to use the “Security.salt” value in the hashing process. In any case, if you need to hash something and be compatible with the Cake’s own Auth and Security, this seems to be the right way.
So make sure you include the second and third parameters of the security function whichever solution you implement.
Below is my version of the code, I have essentially reversed the validation process as I wanted it to only execute if a password confirm was provided (that is the validation wouldn’t be tripped up unless they specifically wanted to change the password, and if the password field itself was blank then nothing should happen ever).
Taken from user.php (the model)
function __construct()
{
parent::__construct();
/*
* Validate on the password field not the password confirmation field
* This ensures that if we enter blank in both then nothing is triggered
* or blank on the confirmation field only then it is rejected using validatePasswdConfirm
*/
$this->validate = array
(
'passwd' => array
(
/* snip other rules */
'match' =>
array
(
'rule' => 'validatePasswdConfirm',
'required' => false,
'allowEmpty' => true,
'message' => __('Passwords do not match', true)
)
),
/*
* On creation when we always want a password, have the form use a normal
* password field and have it validated against it's own special "empty" check
* that is '' that has been hashed (automagically by cake)
*/
'password' => array
(
'rule' => 'validateHashedPassword',
'required' => false,
'allowEmpty' => false,
'message' => __('You must submit a password', true)
)
);
}
function validateHashedPassword($data)
{
if ($data['password'] == Security::hash('', null, true))
{
return false;
}
return true;
}
function validatePasswdConfirm($data)
{
if (isset($this->data['User']['passwd_confirm'])&&
$this->data['User']['passwd'] <> '' &&
$this->data['User']['passwd_confirm'] !== $data['passwd']
)
{
return false;
}
return true;
}
function beforeSave() {
/*
* Ensure that there is a value for the password,
* field it should be ignored if they are not
* providing a value (i.e. no update should take place)
*/
if (isset($this->data['User']['passwd']) && $this->data['User']['passwd'] <> '')
{
$this->data['User']['password'] = Security::hash($this->data['User']['passwd'], null, true);
unset($this->data['User']['passwd']);
}
if (isset($this->data['User']['passwd_confirm']))
{
unset($this->data['User']['passwd_confirm']);
}
return true;
}
Just to be clear too here is my add/edit form (I combine them to save duplication), admin_edit.php:
create('User');?>
end('Submit');?>
Notice too the attribute:
'autocomplete'=>'off'
This just prevents the field from being auto populated which I was experiencing while testing.
If you’re anything like me by the time you’ve got to this guide you have probably read about a million interesting but very issue specific pages on setting up elements of the Auth Component in CakePHP. For such an integral element of the system I found it surprisingly difficult to really understand all of the elements. Getting it working was one thing, but really understanding the “Automagic” functions behind the scenes, what happened where and what would happen if I changed something took a lot of research.
So this guide attempts to bring together the results of that research. It is a combination of the “must reads” , a list of problems and solutions that I encountered when implementing stuff from them, a FAQ section (rather a Q&A of the things I wanted to know along the way) and a general tips section for things that should make your life a bit easier.
The guide will continue to evolve as I discover more but if you have any suggestions or questions please submit a comment below so it is available to all.
Required reading
These links are required reading if you want to get to grips with the Auth component AND ACL. I can’t stress enough how important it is to have read these and done them line by line before you start looking for answers elsewhere, without these examples the Auth component and ACL will be a little too black box.
[p2p type=”id” value=”180″ title=”What is the difference between the Auth component and the ACL component”]
[p2p type=”id” value=”171″ title=”User’s password is double hashed on edit”]
Tips and and tools
Debuging ACL in CakePHP
Some handy tricks for seeing what is going on under the hood, this can be surprisingly tricky when things are happening automagically.
[p2p type=”id” value=”133″ title=”Debugging the CakePHP Auth Component”]
[p2p type=”id” value=”130″ title=”Session variables available when using the CakePHP Auth component”]
Managing ACL in CakePHP
One of the complicating factors in managing your ACL setup is the derth of good management tools available. The data required is fiddly and doesn’t lend itself to hacking around in the database to get things going because in my experience you just make yourself more confused than ever.
It’s not perfect, but it’s a solid tool that will speed up some of your management functions.
Handy functions that play nicely with the Auth Component
Keep track of modifications to your records automatically, these mods save time but also reveal some interesting concepts behind the Authcomponent. Pay close attention to Comment 5 “Brett H Says” which explains an issue you’re likely to encounter using this if you’ve set up the auth component using the standard configuration.
Aran Johnson has done an excellent series of examples and tutorials on the CakePHP auth component here. I found these when I was about half way through writing this guide so there is some duplication of course, but hopefully between the two you will find everything you need.
This is the first in a series of articles on the CakePHP Auth component, for me one of the more complicated elements of CakePHP (once you get past the most basic configuration) and one that has taken me ages to work out. Hopefully the information in these guides will go some way to helping you through this process.
Ok, so here are some tricks for debugging the cakePHP auth component in no particular order, they are not a guide to setting up the CakePHP auth component per se so take a look at those for more general set up information:
Debugging the controller and action being requested by the Auth component
in the beforeFilter() of app_controller.php put:
$this->Auth->authError = sprintf(__('You are not authorized to access that location %s/%s .',true),$this->name,$this->action);
This should be alongside your settings for $this->Auth->loginAction and $this->Auth->loginRedirect for example – assuming you have correctly set up your login form to display $session->flash(‘auth’); then it will show you what was rejected by the Auth component.