Setup LAMP stack on Ubuntu 10.04 the easy way…
July 20, 2010 by Adam · Leave a Comment
Open up a terminal session, type the following:
sudo apt-get install phpmyadmin
Everything else will be taken care of with dependencies. That’s all!
Simple Sluggable Behavior for CakePHP
November 18, 2009 by Adam · Leave a Comment
For quite a while now pretty much the defacto standard for generating automagic slugs in CakePHP has been the Sluggable Behavior by Mariano Iglesias (mariano) in the Cake Bakery (http://bakery.cakephp.org/articles/view/sluggable-behavior). I’ve never quite gotten on with this behavior, it’s not that it doesn’t do what it says, it just seems like it uses a lot of code to do it, and I prefer all of my code to be as simple as possible.
I’ve been using my own Sluggable Behavior for a while, and today I found myself needing a little more customisation, I made a few changes and now it works in exactly the same way as Mariano Iglesias’s, but using less code (and CakePHP’s built in Inflector::slug). So here is my Sluggable Behavior.
<?php
class SluggableBehavior extends ModelBehavior {
/**
* Defaults
*
* @var array
* @access protected
*/
var $_defaults = array(
'seperator' => '-',
'label' => 'title',
'field' => 'slug',
'extension' => '.html',
'length' => 100,
'overwrite' => false
);
/**
* Initiate Sluggable behavior
*
* @param object $Model instance of model
* @param array $config array of configuration settings.
* @return void
* @access public
*/
function setup(&$Model, $config = array()) {
$settings = array_merge($this->_defaults, $config);
$this->_settings[$Model->alias] = $settings;
}
/**
* Before save method. Called before all saves
*
* Overriden to transparently manage creating and populating the slug field
*
* @param AppModel $Model Model instance
* @return boolean true to continue, false to abort the save
* @access public
*/
function beforeSave(&$Model) {
extract($this->_settings[$Model->alias]);
if(!empty($Model->data[$Model->alias][$Model->primaryKey]) && !$overwrite) {
return true;
}
if(!is_array($label) && empty($Model->data[$Model->alias][$label])) {
return false;
}
if(is_array($label)) {
$slug = '';
foreach($label as $key) {
$slug .= ife(!empty($slug), ' ', '') . $Model->data[$Model->alias][$key];
}
} else {
$slug = $Model->data[$Model->alias][$label];
}
$slug = Inflector::slug($slug, $seperator);
$slug = low($slug);
if(strlen($slug) > $length) {
$slug = substr($slug, 0, $length-1);
}
if(!empty($extension)) {
$slug = $slug . $extension;
}
$i = 1;
while($Model->find('count', array('conditions' => array($Model->alias . '.' . $field => $slug))) != 0) {
if (!preg_match ('/'.preg_quote($seperator).'{1}[0-9]+$/', $slug)) {
if(!empty($extension)) {
$slug = $slug . $seperator . ++$i . $extension;
} else {
$slug = $slug . $seperator . ++$i;
}
} else {
$slug = preg_replace('/[0-9]+$/', ++$i, $slug);
}
}
$Model->data[$Model->alias][$field] = $slug;
return parent::beforeSave($Model);
}
}
?>
Usage
var $actsAs = array(
'Sluggable' => array(
'label' => 'name',
'extension' => false
)
);
// or
var $actsAs = array('Sluggable');
Integrating the Auth Component with a bespoke 3rd party Single Sign-On Service in CakePHP
September 14, 2009 by Adam · Leave a Comment
Background
I recently had to develop a marketing portal for a large French car manufacturer. They have a number of internal sites, which are accessed by all internal staff, all sales people at individual dealers, and also external agencies (such as us). They have one central “portal”, which is where a user logs in, and then has links to all of the other portals they have access to. This provides the basis of their “Single Sign-On” Service.
The Single Sign-On Service
Basically, the Single Sign-On Service is all based around this initial “portal” site. Each of the links from this portal to the other sites contained a “token” in the URL, which is an encrypted string containing the userid for the central “users” table, and a timestamp, which is the time the link was generated.
There is then a file (which we don’t have access to) in the server’s include_path which decodes this token, checks the session is valid and looks the user up in the central users table, assuming it was a valid portal token, it then sets all of the user’s details in the session and hands control back to you.
Our task was to integrate this with Cake’s Auth component, so we can cache the users in our local database and do our own application level access control. Using Cake, this was surprisingly easy, once you get past one tiny configuration issue.
Implementation
I got the guys in the webservices department to point the portal link for our site to the domain root. This way we can let Cake handle things automatically…
The first thing we need to do is tell Cake to execute the code inside UsersController::login(). We do that using autoRedirect in the AppController::beforeFilter().
function beforeFilter() {
$this->Auth->autoRedirect = false;
}
This now means Cake won’t try to log you in automatically, but it still redirects you to UsersController::login(), so the token that we had passed gets lost. I initially tried saving this to the Session, but Cake appears to reset this when it redirects to the login function, so I decided to save it in a Cookie instead.
function beforeFilter() {
$this->Auth->autoRedirect = false;
if(!empty($_GET['TOKEN'])) {
$login = array(
'token' => $_GET['TOKEN']
);
$this->Cookie->write('login', $login, false, 60);
}
}
The cookie only lasts 60 sections, as we shouldn’t ever need it for any longer than that.
The next step was the login function itself. We didn’t need to worry about connecting to the central user table, decrypting the token, or worrying about validity, as the login script provided on the server would deal with that.
function login() {
if ($this->Auth->user() == null) {
App::import('vendor', 'do_login', 'do_login/do_login.php');
$user = $this->_register_user();
if($user) {
if($this->Auth->login($user)) {
$this->redirect($this->Auth->redirect());
} else {
// this shouldn't happen if do_login.php does it's job!
}
} else {
// this should only happen if there's an error with the db
}
} else {
$this->redirect($this->Auth->redirect());
}
}
The above code first checks to see if the user is current logged into Cake, if they are, it just allows them to continue back to where they were. This might happen if they accidently close the window, and go back to the central portal to login.
If the user isn’t logged in, the first thing we do is include the provided login script. We can safely assume that once this has run, we have a valid user in the system. Next we call our private _register_user() function, which does a little bit of Cake trickery:
function _register_user() {
$sso_data = $this->Session->read('sso');
$this->Session->del('sso');
if (!empty($sso_data['internal']) && $sso_data['internal'] == true) {
$group = $this->User->Group->findByDefaultFor('internals');
$group = $group['Group']['id'];
$username = $sso_data['ddb_id'] . ".int";
} elseif(!empty($sso_data['agency']) && $sso_data['agency'] == true) {
$group = $this->User->Group->findByDefaultFor('agencies');
$group = $group['Group']['id'];
$username = $sso_data['ddb_id'] . ".agy";
} else {
$group = $this->User->Group->findByDefaultFor('internals');
$group = $group['Group']['id'];
$username = $sso_data['ddb_id'] . ".dlr";
}
$user = array(
'User' => array(
'username' => $username,
'password' => Security::hash($sso_data['ddb_id']),
'firstname' => $sso_data['forename'],
'lastname' => $sso_data['surname'],
'email' => $sso_data['email'],
'group_id' => $group
)
);
if($this->User->findCount(array('User.username' => $username)) == 0) {
$this->User->create();
if($this->User->save($user)) {
return $user;
} else {
return false;
}
} else {
return $user;
}
}
Taking this step by step, the first thing we do is read the user’s details from the Session. We can then safely delete this, as we shouldn’t need it again and don’t really want it there.
The next few lines check to see if the user is a dealer, an internal, or an agency user, and assigns them the appropriate group. We also set them up with a username for use with our Cake App which we know will be constant (central user database record id), and append a string to visually identify what type of user they are (this is just for our ease of use when reading the db).
We can then setup the Cake user array, using our new username and group, and the details from the Single Sign-On service. We use the central user database record id again as the password, it doesn’t matter what this is as at this point we know we already have a valid user, we just want something we can pass to Cake to log the user in. After a quick check to see if the username currently exists, we either save the new user and return the User details, or, we just return the details as they are.
This should leave us with a valid Cake User record in $user, which we can then pass to $this->Auth->login(), as if we were doing an ajax login, and finally redirect the user back to where they were.
One final pitfall – if a Cake login request comes from a 3rd party site, and you have security set to medium or high in config.php, the login will fail. You have to set your security to low for this to work. This isn’t the ideal situation, obviously we would prefer a security setting of “high”, but as our application doesn’t need to check the validity of the user we thought this was acceptable (in this instance).
How to alternate table row colours the easy way
September 11, 2009 by Adam · Leave a Comment
We’ve all seen the familiar code in our baked index files:
<?php
$i = 0;
foreach ($pricings as $pricing):
$class = null;
if ($i++ % 2 == 0) {
$class = ' class="altrow"';
}
?>
<tr <?php echo $class; ?>>
I’ve always thought that this wasn’t quite as elegant as it should be, and certainly not very DRY, as it was required everywhere you wanted alternate row colours. Well today I came across this nice little CakePHP helper by @markgandolfo for alternating table colours in a very similar way to Rails, which takes the above code and makes it look more like this:
<table> <?php foreach($items as $item): ?> <tr class="<?php echo $cycle->css(); ?>"> <td><?php echo $item; ?></td> </tr> <?php endforeach; ?> </table>
Much better.
Check it out here: http://github.com/markgandolfo/cakephp_cycle_css/tree/master