Tracking navigation history of a user

by janb
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$position1);
        }
    }

}
?>

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.

Report

More on Components

Advertising

Comments

  • blank posted on 06/28/10 02:45:31 AM
    Hello,
    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
  • arvindk posted on 09/14/09 03:14:55 AM
    Hi All,

    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.
  • valiant64 posted on 06/27/09 05:58:54 PM
    Thanks for this thread everyone. It gave me the necessary tools to allow navigation control. My app shoots off a lot of emails, with direct links. Usually when they get to the link, they are not logged in. So here's my solution on redirecting to the login page, then further to their original request.

    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.
  • bravofoxtrot posted on 06/09/08 12:23:51 PM
    Has there been any developments with this component lately? I was hoping to use this component with the latest 1.2 RC1 code but 'bare' is deprecated so this component generates errors.

    What is the 1.2 equivalent to params['bare']?
    • bilson posted on 07/29/08 05:09:34 PM
      Has there been any developments with this component lately? I was hoping to use this component with the latest 1.2 RC1 code but 'bare' is deprecated so this component generates errors.

      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']))) {

  • janb posted on 09/25/07 03:24:51 PM
    In the first place I want to thank everybody for your feedback and your compliments. As said this is just a quickly built component. I will try to implement the suggested changes.

    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...
  • maninderv posted on 09/20/07 08:14:56 AM
    This looks like a really promising component.

    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.
    • neilc posted on 09/20/07 08:28:56 AM
      Neil, did you make changes to fix issues you addressed?
      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.
  • nikchankov posted on 09/12/07 01:34:10 PM
    I just login to say exactly the same thing as Neil - you should give possibility this helper to store History for different part's of the site.
    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.
  • neilc posted on 09/12/07 03:42:35 AM
    Nice component, I should wrap my functionality up in one I guess. Here's how I do it. Feel free to comment.

    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) > $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 " (This is why I call it in beforeRender, so controller->pageTitle is set).

    By the way, apologies for the incomprehensible title for this comment - commas are invalid apparently.
  • nikchankov posted on 09/11/07 03:33:46 AM
    I took a look and it's working for me except 2 things:
    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:

    $this->data = $controller->Session->read($this->variable.'.history');
    and

    $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:


    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;
            }
        }
    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:
    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.
login to post a comment.