Saturday, March 28, 2009

Zodiac Sign Helper Class for CakePHP

Saturday, March 28, 2009 0
This is a simple Helper class for CakePHP that determines what is the zodiacal sign that corresponds to a given date or datetime string.

Releases:

  • Major version released. 1.0.0.0 (New!)

Requirements:

  • CakePHP 1.2 (not tested with CakePHP 1.1.x.x)
  • PHP versions 4 and 5

Licese:

Download:

Installation:

Example Usage:

In your view, just call the ZodiacSignHelper::name(). The passed argument must be a valid date or datetime string. And you will get a Sun zodiac sign such as Virgo, Leo and Sagittarius:

echo $zodiacSign->name($data['User']['birthday']);

You can also get a Chinese zodiac sign by setting the second parameter to 'Chinese'. The Chinese Zodiac consists of a 12-year cycle, each year of which is named after a different animal that imparts distinct characteristics of its year. For example, the year 2009 is the Year of the Ox:

echo $zodiacSign->name($data['User']['birthday'], 'Chinese');

Thursday, March 26, 2009

A More Secure Way to Transfer Session State Between CakePHP Applications

Thursday, March 26, 2009 2

In a previous article entitled "Sharing Session State Across CakePHP Applications", I wrote about the way to transfer session IDs between CakePHP applications. Thanks to the CakePHP development team, CakePHP already has secure session handling; however, some might even think that it is not secure to append a session ID to links. I hear say some search engines indexes URLs with session IDs. Isn't it horrible? One obvious solution would be not to assign them to any of the links on pages.

Let's say we have two sites, siteA.com and siteB.com. We need to maintain a user's session state (authenticated with the Auth Component) when the user jumps from siteA.com to siteB.com by clicking some link.

First, we need to do some settings:

  • Set 'Security.level' to 'low' on siteB.com.
  • Set the session handling method ('Session.save' in app/config/core.php) to 'database'.
  • Use the same Security.salt (/app/config/core.php) for each application.

We are going to use the same session database table for both sites.

Use the CakePHP console to create your session database table:

$ cake schema run create Sessions

Yeah, it's always fun to run the cake console, but you can also use the SQL file found in app/config/sql/sessions.sql.

Create a SiteTransfer Model for each application (on siteA.com and siteB.com):

class SiteTransfer extends AppModel {
    var $name = 'SiteTransfer';
}

Basically the table structure looks something like this:

create table site_transfers (
 id varchar(36) not null,
 sess_id varchar(26) not null,
 primary key (id)
);

In our Users Controller on siteA.com:

class UsersController extends AppController {

  var $name = 'Users';
  
  function index() {}

  function redirectem() {
    $this->autoRender = false;
    App::import('Core', 'String');
    $data['SiteTransfer']['sess_id'] = $this->Session->id();
    $this->SiteTransfer->id = String::uuid();
    if($this->SiteTransfer->save($data)) {
      $this->redirect(
        'http://siteB.com/users/catchem?uuid='.$this->SiteTransfer->id
      );
    }
  }

} 

We have a link saying ‘Go to siteB.com’in the index.ctp view:

echo $html->link('Go to siteB.com', array(
  'action' => 'redirectem'
));

Let's see what's going on here…
When the user on the Index page on siteA.com clicks the link, we redirect the user to the redirectem() action (this action does not need any view). In the 'redirectem' method, we get the current session ID and save it into our site_transfers table with a UUID (i.e. String::uuid). Then we do a redirect to /users/catchem on siteB.com.

Alright, let's build a Users Controller for siteB.com:

class UsersController extends AppController {

  var $name = 'Users';
  var $components = array('Session');

  function beforeFilter() {
    if(!empty($this->params['url']['uuid'])) {
      $uuid = $this->params['url']['uuid'];
      $data = $this->SiteTransfer->findById($uuid);
      $this->Session->id($data['SiteTransfer']['sess_id']);
      $this->SiteTransfer->del($uuid);
    }
  }
  
  function catchem() {
    $this->redirect(array('action' => 'index'));
  }

  function index() {
    pr($this->Session->read('Auth')); 
    exit; 
  }

} 

What happens when the user gets to siteB.com?.
We search our database table (site_transfers) for the UUID token, and then instantiate the session with the session ID from the database. Finally, for security purpose, we need to delete the UUID and session ID from our site_transfers table.

All done! So now the user is logged into both siteA.com and siteB.com.

Saturday, March 21, 2009

Sharing Session State Across CakePHP Applications

Saturday, March 21, 2009 0

This is a pretty simple tip, but I thought I might want to document this somewhere like the gotcha page. I'll show you how to share session state across multiple CakePHP applications. It's as easy as 1-2-3.

Let's say we have two sites, siteA.com and siteB.com. A user is browsing siteA.com and we want him (or her) transferred to siteB.com. The user should be already authenticated before jumping to siteB.com.

There are some settings you must first configure:

  • Make sure that you have set 'Security.level' to 'low' on siteB.com. Notice that 'high' and 'medium' will mark the embedded session ID as invalid.
  • Set the session handling method ('Session.save' in app/config/core.php) to either 'php' or 'database'. Both applications must have the same session handling method and access to the same session storage (and therefore the same session).
  • Use the same Security.salt (/app/config/core.php) for each application.

In our view template on siteA.com, append the session ID to the link like the following:

echo $html->link('Go to siteB.com',
 "http://siteB.com/tests/index?sid=" . $session->id()
);
On the other end (siteB.com), use $this->Session->id($this->params['url']['sid']) in the beforeFilter method of your controller:
function beforeFilter() {
  if (!empty($this->params['url']['sid'])) {
    $this->Session->id($this->params['url']['sid']);
  }
}

When the user clicks the link on siteA.com, it'll redirect with the session id as parameter and instantiate a new session.

If you need a more secure way, go check the next post “A More Secure Way to Transfer Session State Between CakePHP Applications

Monday, March 16, 2009

Utilizing the AppController::beforeRender to Assign CakePHP's Controller Attributes

Monday, March 16, 2009 0
This may be a matter of preference, but I think it's a pain to assign CakePHP's controller attributes to all views in my controllers. So I always do this in my applications.

Add a $this->set into the AppController::beforeRender to always read $this->data.
class AppController extends Controller {
function beforeRender() {
 if (!isset($this->viewVars['data'])) {
  $this->set('data', $this->data);
 }
}
}
In your controller (any controller that extends the app controller), you don't have to assign $this->data any more.
class PostsController extends AppController {
function index() {
 $this->data = $this->paginate();
}
}
In this manner, you can also do something like the following:
function beforeRender() {
 if (!isset($this->viewVars['data'])) {
  $this->set('data', $this->data);
 }
 if (!isset($this->viewVars['modelClass'])) {
  $this->set('modelClass', $this->modelClass);
 }
}
Now, you can access them anywhere in views with $data and $modelClass.
<? if($data): ?>
<? pr($data)?>
<? endif; ?>

<? if($modelClass): ?>
<? pr($modelClass)?>
<? endif; ?>

Monday, March 9, 2009

How To Paginate Self-Referential HABTM Relationships in CakePHP 1.2

Monday, March 9, 2009 6
Cakebaker (Daniel Hofstetter) once wrote a great article entitled, "Pagination of data from a HABTM relationship". The article discuss how to do CakePHP Pagination with a HABTM relationship. Inspired by his article I want to show how to paginate self-referential HABTM relationships. This implementation has been tested with CakePHP 1.2.1.8004 and won’t work with older versions.

Let’s say you have a User model, and you want to paginate the Friends of a certain User. Your table definition looks like:
create table users (
id int(11) not null auto_increment,
username varchar(32) not null,
primary key (id)
);

create table users_friends (
id int(11) not null auto_increment,
user_id int(11) not null,
friend_id int(11) not null,
primary key (id)
);

We can then add some test data to the tables:
insert into users (id, username) values (1, 'kevin');
insert into users (id, username) values (2, 'stephanie');
insert into users (id, username) values (3, 'michael');
insert into users (id, username) values (4, 'jennifer');

insert into users_friends (user_id, friend_id) values (1, 2);
insert into users_friends (user_id, friend_id) values (1, 3);

insert into users_friends (user_id, friend_id) values (2, 1);
insert into users_friends (user_id, friend_id) values (2, 4);

insert into users_friends (user_id, friend_id) values (3, 1);
insert into users_friends (user_id, friend_id) values (3, 4);

insert into users_friends (user_id, friend_id) values (4, 2);
insert into users_friends (user_id, friend_id) values (4, 3);

And make a self-referential join in your User model:
class User extends AppModel {
var $name = 'User';
var $hasAndBelongsToMany = array(
'Friend' => array(
'className' => 'User',
'joinTable' => 'users_friends',
'foreignKey' => 'user_id',
'associationForeignKey' => 'friend_id',
'unique' => true,
)
);
}

Now we want to paginate all friends in the user “kevin”. By following the tutorial "Quick Tip - Doing Ad-hoc Joins in Model::find()", we’ll create a controller:
// app/controllers/friends_controller.php
class FriendsController extends AppController {
function index() {
$this->paginate = array(
'Friend' => array(
'limit' => 2,
'joins' => array(
array(
'table' => 'users_friends',
'alias' => 'UsersFriend',
'type' => 'inner',
'conditions'=> array(
'UsersFriend.friend_id = Friend.id',
),
),
array(
'table' => 'users',
'alias' => 'User',
'type' => 'inner',
'conditions'=> array(
'User.id = UsersFriend.user_id',
'User.id' => 1
)
)
)
)
);
$data = $this->paginate('Friend');
debug($data);
exit;
}
}

The debug output should now look like:
Array
(
[0] => Array
(
[Friend] => Array
(
[id] => 2
[username] => stephanie
[0] => Array
(
[id] => 1
[username] => kevin
[UsersFriend] => Array
(
[id] => 3
[user_id] => 2
[friend_id] => 1
)
)
[1] => Array
(
[id] => 4
[username] => jennifer
[UsersFriend] => Array
(
[id] => 4
[user_id] => 2
[friend_id] => 4
)
)
)
)
[1] => Array
(
[Friend] => Array
(
[id] => 3
[username] => michael
[0] => Array
(
[id] => 1
[username] => kevin
[UsersFriend] => Array
(
[id] => 5
[user_id] => 3
[friend_id] => 1
)
)
[1] => Array
(
[id] => 4
[username] => jennifer
[UsersFriend] => Array
(
[id] => 6
[user_id] => 3
[friend_id] => 4
)
)
)
)
)

Friday, March 6, 2009

About this Channel

Friday, March 6, 2009 0
I'm one of the millions living day to day, a less ordinary life. I'm a web software engineer from Japan and here I write about anything that strikes me. I mainly do PHP with CakePHP, MySQL, CSS, and Javascript. Nothing is sugar coated, nothing is fantasy. This is as real as it gets. This is my life. Kyo is my nickname for me, Yasuhiro Sota. It's not a code, it's me.
 
JamNite ◄Design by Pocket, BlogBulk Blogger Templates