Tracking navigation history of a user
Many times it can be very useful to track the navigation history of a user. Especially if you want to redirect the user to a page where he came from after some action (e.g. login). With this HistoryComponent, it's extremely easy to handle such actions.
Store the component code in: controllers/components/history.php.
Component Class:
<?php
/**
* Maximum size of the array containing the user navigation history
*/
define('STUDIOSIPAK_MAX_HISTORY', 10);
/*
* HistoryComponent: User navigation history
* @author: Studio Sipak
* @website: http://webdesign.janenanneriet.nl
* @license: MIT
* @version: 0.1
* */
class HistoryComponent extends Object
{
var $data = array();
var $started = false;
var $controller = true;
function startup(&$controller) {
// This test will prevent it from running twice.
if(!$this->started) {
$this->started = true;
$this->controller = $controller;
$this->data = $controller->Session->read('User.history');
if($controller->params['bare'] == 0) {
$this->_addUrl($controller->params);
}
$controller->Session->write('User.history', $this->data);
}
}
function goBack($step = 1) {
$pos = count($this->data) - $step - 1;
$this->controller->redirect($this->data[$pos]);
exit();
}
function show() {
return $this->data;
}
function _addUrl($params) {
count($params['url']) ? $url = '/'.$params['url']['url'] : $url = '/';
if(count($this->data) == STUDIOSIPAK_MAX_HISTORY) {
$this->_deleteUrl();
}
$this->data[] = $url;
}
function _deleteUrl($position = 0) {
if($position == 0) {
array_shift($this->data);
}
else {
array_splice($this->data, $position, 1);
}
}
}
?>
Usage
The component will automatically be loaded by the Controller. In your controller you can use the functions goBack($position) and show(). The function goBack() will send the user x many pages back in his navigation history. If you don't specify a number, it will be the last page. The function show() gives an overview of the navigation history.
class YourController extends AppController
{
function doSomething() {
...
// Redirect user to previous page
$this->History->goBack();
}
}
Expansion
This version is just my first one, so it is might not be complete yet. It is not difficult to expand the component for more advanced usage. Comments regarding more functionality and bugs are welcome.

I've added the history.php file in the components subfolder and I have added History as a component in app_controller, but I'm getting the following error message:
Notice (8): Undefined index: bare [APP/controllers/components/history.php, line 25]
It's these lines:
if($controller->params['bare'] == 0)
{
$this->_addUrl($controller->params);
}
Any ideas?
Andrew
The path saving in form of "$url = $this->here" seems buggy there. I am working on my localhost and my document root is "C:/htdocs/cardclub" and url "localhost/cardclub". My history urls are saving as history[0]['url'] = "/cardclub/tournaments/book_tickets/3/2009-09-14" etc. so when i redirect it after login using $this->redirect($history[0]['url']) it tries to redirect it to "http://localhost/cardclub/cardclub/tournaments/book_tickets/3/2009-09-14" which is incorrect. I had to find a quick hack to it and the following code is still working just fine for me.
To make it work comment out:
$url = $this->here; to look like
//$url = $this->here;
and replace:
if(count($params)>1) foreach ($params as $param => $value) if($param!='url') $query[] = $param.'='.$value; //Adds query string params to url
with
if(count($params)>1) {
foreach ($params as $param => $value) {
if($param!='url') {
$query[] = $param.'='.$value;
} else {
$url = "/".$value;
}
}
} else {
$url = "/".$params['url'];
}; //Adds query string params to url
Let me know if you have a better solution.
App Controller - beforeFilter()
// History Markings
$hisSize = 10; // Maximum History Size
$history = $this->Session->read('history');
if(!$history) $history = array();
while(count($history) > $hisSize - 1) {
$trash = array_shift($history);
}
array_push($history, $this->here);
$this->Session->write('history', $history);
Login function -- after all your validation
$history = $this->Session->read('history');
if(count($history)) {
$red = array_pop($history);
while(substr($red, 0, 13) == '/users/login/') {
$red = array_pop($history);
}
} else {
$red = "/servicerequests/index/projects";
}
$this->Session->redirect($red);
I hope this helps any who are looking for something similar.
What is the 1.2 equivalent to params['bare']?
In function "__savePageHistory", change:
if(!empty($this->here)
&& $this->params['bare']==0) {
To
if(!empty($this->here)
&& (!isset($this->params['bare']))) {
The URL things as posted by Nik, were already known. I'll definitely change these. I'll have a closer look at Neil's comments to see how these can be included. Stay tuned for my update...
Based on feedback from Neil, have there been updates to this component?
Neil, did you make changes to fix issues you addressed?
Can you share with us?
Thanks,
Mandy.
I haven't made any changes to the original component posted above by boonen, but the code I posted should work on its own. If you want to wrap it up inside a component, feel free and post it back here. I'm sure many people would appreciate it.
I already implemented your Helper with my proposed changed, and it's working so far, but that example drive me to write this:
I went to my site, let's say http://my.dommain.com/, then I decided directly to login into admin and I add http://my.domain.com/admin/ and when I fill user and pass I was redurected to http://my.domain.com/ it is because the Helper does'nt detect the part admin. The solution is either to follow the Neil's way or to add a function which will force storing the step determined by some condition.
In my AppCotnroller
<?php
class AppController extends Controller {
var $savePageHistoryCms = true;
var $savePageHistorySite = true;
function beforeRender () {
if($this->savePageHistoryCms && defined('CAKE_ADMIN') && isset($this->params[CAKE_ADMIN]) && $this->params[CAKE_ADMIN] = CAKE_ADMIN) {
$this->__savePageHistory('cms');
} elseif($this->savePageHistorySite) {
$this->__savePageHistory('site');
}
}
/**
* Handles adding or removing of page histories to session stack
* Works for both site and cms page histories.
*
* @param string $path Prefix for session array, normally 'site' or 'cms'
*/
function __savePageHistory($path) {
if(!empty($this->here)
&& $this->params['bare']==0) {
$url = $this->here;
$params = $this->params['url'];
$query = array();
if(count($params)>1) foreach ($params as $param => $value) if($param!='url') $query[] = $param.'='.$value; //Adds query string params to url
if(count($query)) $url .= '?'.implode('&',$query);
$title = strlen($this->pageTitle) > 0 ? $this->pageTitle : 'Previous page';
if($this->Session->valid()
&& $this->Session->check($path.'_page_history')) {
$page_history = $this->Session->read($path.'_page_history');
if(isset($page_history[0]['url'])) {
if(isset($page_history[1]['url']) && $url == $page_history[1]['url']) {
array_shift($page_history);
} elseif($url != $page_history[0]['url']) {
array_unshift($page_history,array('url'=>$url,'title'=>$title));
}
} else {
$page_history = array(0=>array('url'=>$url,'title'=>$title));
}
} else {
$page_history = array(0=>array('url'=>$url,'title'=>$title));
}
$this->Session->write($path.'_page_history',$page_history);
}
}
}
If you don't separate out the admin and site histories, and someone has the back end open in one browser tab, the front end open in another, and between loading an edit screen in the back end and submitting the form, the user has checked something on the front end in the other tab, when they do submit the back end form they end up getting redirected to the page in the front end!
If you store the title as well, you can have back links in your views that say "Back to
By the way, apologies for the incomprehensible title for this comment - commas are invalid apparently.
1. in my authentication Session variable 'User' is for storing User record and check if user is logged in is
if(!is_array($this->Session->read('User')))
well with your way this directly made User as array and ofcourse my Auth was broken. It is fine - my auth is not so complex and I could modify -it's mine, but third party components is difficult to touch - especially if you plan to update them regulary.
So, my first suggestion is: parametrize Session variable:
like:
and$this->data = $controller->Session->read($this->variable.'.history');
$controller->Session->write($this->variable.'.history', $this->data);
and ofcourse $this->variable could be defaulted to 'User' in Class vars.
2. You don't track the same url. It's like when you press refresh of the page. In this case on 2-3 Refreshes of the page and you couldn't know which step you need to point for return. I'd suggest this fix:
briefly it check if the current url is the same as the last one and if so, don't insert it into the array. It's possible to be improved further more because now when I access:function _addUrl($params) {
count($params['url']) ? $url = '/'.$params['url']['url'] : $url = '/';
if($url != $this->data[count($this->data)-1]){
if(count($this->data) == STUDIOSIPAK_MAX_HISTORY) {
$this->_deleteUrl();
}
$this->data[] = $url;
}
}
http://myserver/controller
and I submit let's say login which in action has /controller/
in the session var will be stored:
[0]=>'/controller'
[1]->'/controller/'
which again is duplication. If you make this as well I think this is component would be very usefull especially in Login actions where the user will be returned on the page which he requested before auth.
Hope this make sense.