Where did the last month go!? As explained in an earlier post Moodle URLs don’t make for very good tracking using traditional server logs or Google Analytics (GA). We have implemented a fix using pseudo-URLs for GA and here I’ll show the basics of code we’ve used (minus some institution-specific tweaks).

We’re aiming to create a pseudo-URL similar to:
{school}.{role}.{course}.{activity-action}.{id}

All the code is in a section in the head part of our theme’s header.html (Moodle 1.9x) or layout/general.php etc (Moodle 2.x) plus any other layout pages that you want to track. Actually, the code is stored in an include and just included where needed.

To set the scene, Moodle authenticates against Active Directory and gets some data from this into the global $USER on login. So we need this:

  // get user department code
  if (isset($USER->department)) {
    $userDept =   trim($USER->department);
  } else { 
    $userDept = ''; 
  }

Next, we have a bit of code that sorts these department codes into school codes ($userSchool). I won’t show these but they result in values like CASS, SOI, SEMS etc. You could just as easily set up a variable here that gets the category code, like this:

  // get category ID
  $category = $COURSE->category;

Getting the user’s role in context is a bit trickier, we’re really only interested in the highest level role at course context level for our purposes but I suppose you could adapt to get a role for the activity context:

  // get role of user for the course context (level=50)   
  $gaUserID = $USER->id;
  $gaRole = 'unknownrole'; //default
  if ($gaUserID > 0) { 
    // get contextID for this course first
    $gaQry = get_record('context','instanceid',$COURSE->id,'contextlevel','50');
    $gaContextID = $gaQry->id; 
    // then count records to see if user has an entry in role_assignments table
    $gaQryCnt = count_records('role_assignments','contextid',$gaContextID,'userid',$USER->id);
    // note a user may have multiple roles in db so get highest one  
    if ($gaQryCnt > 0) { // ok, there are entries so get role
      $gaQry = get_records_sql("SELECT * FROM m_role_assignments WHERE contextid='" . $gaContextID . "' AND userid='" . $USER->id . "' ORDER BY roleid ASC",0,1);
      $gaQryObj = $gaQry[key($gaQry)]; // get key for 1st element in associative array
      $gaQry = get_record('role','id',$gaQryObj->roleid);  // get role name
      $gaRole = $gaQry->shortname;
    } else { // no specific course role, then what are they then?
      if ($gaUserID == 1) $gaRole = 'guest';  // assuming standard Moodle setup!
      if ($gaUserID == 2) $gaRole = 'superadmin';  // assuming standard Moodle setup!
      // is user a Moodle admin?
      $gaQryCnt2 = count_records('role_assignments','contextid',1,'userid',$USER->id);
      if ($gaQryCnt2 > 0) $gaRole = 'superadmin';
    }
  } else {
    $gaRole = 'notloggedin';
  }

Now, to get the module shortname … this bit’s easy:

  // get course shortname, could also use id, fullname, 
  // idnumber although latter not always given
  $gaCourse =  $COURSE->shortname;

Next we need the activity type and the action being undertaken, this could be split into several variables if required but we are happy with values like: mod-resource-view.

Getting useful info here required a lot of digging into how various activities constructed URLs, there is a lot of variation and custom request variables which are not all covered here (e.g. Turnitin):

  // work out what major task is being done by looking at raw URL
  $gaPagePath = $CFG->pagepath;
  $gaTask = $gaPagePath;
  $gaURLParts = explode('/',$gaPagePath); 
  $gaURLPartsCount = count($gaURLParts); 
  // we're interested in array elements 3 and above, the bits after Moodle root URL
  // so if URL is http://moodle.college.ac.uk/course/view.php?id=102
  // element 3 = 'course', 4 = 'view.php'   (except for some admin pages, see below)
  // following bit checks down the path and grabs relevent keywords to build task
  if ($gaURLPartsCount > 3) {
    $gaTask = $gaURLParts[3];
    if ($gaTask == '') $gaTask = 'home';
  }
  if ($gaURLPartsCount > 4) { 
    if ($gaURLParts[4] != '') {
      if (strpos($gaURLParts[4],'.') > 0) {
        $gaTask = $gaTask . '-' . substr($gaURLParts[4],0,strpos($gaURLParts[4],'.'));
      } else {
      $gaTask = $gaTask . '-' . $gaURLParts[4];
      }
    }
  }
  if ($gaURLPartsCount > 5) {
    if ($gaURLParts[5] != '') {
      if (strpos($gaURLParts[5],'.') > 0) {
        $gaTask = $gaTask . '-' . substr($gaURLParts[5],0,strpos($gaURLParts[5],'.'));
      } else {
        $gaTask = $gaTask . '-' . $gaURLParts[5];
      }
    }
  }
  
  // fix for moodle bug where admin urls are not getting reported correctly in $CFG->pagepath
  // http://tracker.moodle.org/browse/MDL-20342
  $gaTask = str_replace('/','-',$gaTask);

  // fix for help
  if ($gaTask == 'help.php') $gaTask = 'help'; 
                                    
  // if there is a current action specified then append that to task but only for non admin stuff
  if ($gaURLParts[0] != 'admin') {
    if (isset($_REQUEST['currentaction'])) $gaTask = $gaTask . '-' . $_REQUEST['currentaction'];
    // if there is a section specified then append that to task
    if (isset($_REQUEST['section'])) $gaTask = $gaTask . '-' . $_REQUEST['section'];
    // if there is an action specified then append that to task
    if (isset($_REQUEST['action'])) $gaTask = $gaTask . '-' . $_REQUEST['action'];
    if ( (isset($_REQUEST['frameset'])) && (isset($_REQUEST['page'])) ) $gaTask = $gaTask . "-ims-p" . $_REQUEST['page'];
  }

Now, the last part of the URL, the task or page ID:

  // now get task or page ID however best, this list not exhaustive
  $gaTaskID = '';
  // get id number if page has id=xx argument
  if (isset($_REQUEST['id'])) $gaTaskID = $_REQUEST['id'];
        
  // if $gaTaskID still 0 try looking for mod id in URL
  // e.g. mod/forum/post.php?forum=7 or admin/roles/assign.php?contextid=27
  // note that these URLS work best for course content related stuff not user or admin stuff
  // this list is not exhaustive, just the ones I managed to identify
  if ($gaTaskID == '') {
    if (($gaURLPartsCount > 4) && ($gaURLParts[4] == 'forum') && (isset($_REQUEST['f']))) $gaTaskID =  $_REQUEST['f'];if (($gaURLPartsCount > 4) && ($gaURLParts[4] == 'forum') && (isset($_REQUEST['f']))) $gaTaskID =  $_REQUEST['f'];
    if (($gaURLPartsCount > 4) && ($gaURLParts[4] == 'glossary') && (isset($_REQUEST['g']))) $gaTaskID =  $_REQUEST['g'];
    if (($gaURLPartsCount > 4) && ($gaURLParts[4] == 'forum') && (isset($_REQUEST['forum']))) $gaTaskID =  $_REQUEST['forum'];
    if (($gaURLPartsCount > 4) && ($gaURLParts[4] == 'resource') && (isset($_REQUEST['r']))) $gaTaskID =  $_REQUEST['r'];
    if (($gaURLPartsCount > 4) && ($gaURLParts[4] == 'quiz') && (isset($_REQUEST['cmid']))) $gaTaskID =  $_REQUEST['cmid'];
    if (($gaURLPartsCount > 4) && ($gaURLParts[4] == 'quiz') && (isset($_REQUEST['q']))) $gaTaskID =  $_REQUEST['q'];
    if (($gaURLPartsCount > 3) && ($gaURLParts[3] == 'calendar') && (isset($_REQUEST['course']))) $gaTaskID =  $_REQUEST['course'];
    if (($gaURLPartsCount > 3) && ($gaURLParts[3] == 'user') && (isset($_REQUEST['contextid']))) $gaTaskID =  $_REQUEST['contextid'];
    if (($gaURLPartsCount > 3) && ($gaURLParts[3] == 'blog') && (isset($_REQUEST['filterselect']))) $gaTaskID =  $_REQUEST['filterselect'];
    if (($gaURLPartsCount > 3) && ($gaURLParts[3] == 'blog') && (isset($_REQUEST['postid']))) $gaTaskID =  $_REQUEST['postid'];
    if (($gaURLPartsCount > 3) && ($gaURLParts[3] == 'blog') && (isset($_REQUEST['userid']))) $gaTaskID =  $_REQUEST['userid'];
    if (($gaURLPartsCount > 3) && ($gaURLParts[3] == 'help.php') && (isset($_REQUEST['file'])) && (isset($_REQUEST['module']))) $gaTaskID =  $_REQUEST['module'] . '/' . $_REQUEST['file'];
  }
  // still no taskID? try update=xx from editing resource
  if ( ($gaTaskID == '') && (isset($_REQUEST['update']))) $gaTaskID = $_REQUEST['update'];
        
  // if still no taskID then set to 0
  if ($gaTaskID == '') $gaTaskID = '0';

Finally, construct the pseudo-URL and set GA account #:

  // now construct pseudo URL using . separator    
  $pseudoUrl = $userSchool . '.' . $gaRole . '.' . $gaCourse . '.' . $gaTask . '.' . $gaTaskID;
  $pseudoUrl = strtolower($pseudoUrl); // all lower case for neatness
  $gaAccount = "UA-xxxxxxxx-1"; // enter your GA account code here

PHP stuff over, in the html part of the header

    <script>     
      // Google Analytics code as a function instead of inline
      function gaSSDSLoad (acct,pseudo_url) {  
        var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
        var s,pageTracker;  
        s = document.createElement('script');  
        s.src = gaJsHost + 'google-analytics.com/ga.js';  
        s.type = 'text/javascript';  
        s.onloadDone = false;  
        function init () { 
          try { pageTracker = _gat._getTracker(acct); } catch (err) {}
          pageTracker._trackPageview(pseudo_url);
        }
        s.onload = function () {    
          s.onloadDone = true;    
          init();  
        };
        s.onreadystatechange = function() {    
          if (('loaded' === s.readyState || 'complete' === s.readyState) && !s.onloadDone) {      
            s.onloadDone = true;      
            init();    
          }  
        };   
      document.getElementsByTagName('head')[0].appendChild(s);
      }  
     
      // now run script using GA account # and pseudo_url
      // fix for IE8
      if(window.addEventListener) {
        /* W3C method. */
        window.addEventListener('load', function(){gaSSDSLoad ("<? echo $gaAccount; ?>","<? echo $pseudoUrl; ?>") }, false);
      } else if(window.attachEvent) {
        /* IE method. */
        window.attachEvent('onload', function(){gaSSDSLoad ("<? echo $gaAccount; ?>","<? echo $pseudoUrl; ?>") });
      } else {
        /* Old school method. */
        window.onload = function() {gaSSDSLoad ("<? echo $gaAccount; ?>","<? echo $pseudoUrl; ?>"); };
      }
    
    </script> 

File attached with code, don’t forget to edit GA account #
code_snippet

Advertisements