From 5b996938c22b231696d88613612071bb094e09c0 Mon Sep 17 00:00:00 2001
From: Roberto Montero <roberto.montero@achieveinternet.com>
Date: Wed, 12 Jan 2011 14:59:02 -0800
Subject: [PATCH] Changes to the clickpath module

---
 .../modules/contrib/clickpath/clickpath.admin.inc  |   62 +++
 .../all/modules/contrib/clickpath/clickpath.module |  427 +++++++++++++-------
 .../modules/contrib/clickpath/clickpath.theme.inc  |   16 +
 .../clickpath/clickpath_cck/clickpath_cck.info     |    6 +
 .../clickpath/clickpath_cck/clickpath_cck.module   |  136 +++++++
 .../clickpath_taxonomy/clickpath_taxonomy.info     |    6 +
 .../clickpath_taxonomy/clickpath_taxonomy.module   |   24 ++
 .../contrib/clickpath/js/clickpath.admin.js        |   55 +++
 8 files changed, 590 insertions(+), 142 deletions(-)
 create mode 100644 sites/all/modules/contrib/clickpath/clickpath.admin.inc
 create mode 100644 sites/all/modules/contrib/clickpath/clickpath.theme.inc
 create mode 100644 sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.info
 create mode 100644 sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.module
 create mode 100644 sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.info
 create mode 100644 sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.module
 create mode 100644 sites/all/modules/contrib/clickpath/js/clickpath.admin.js

diff --git a/sites/all/modules/contrib/clickpath/clickpath.admin.inc b/sites/all/modules/contrib/clickpath/clickpath.admin.inc
new file mode 100644
index 0000000..64233d4
--- /dev/null
+++ b/sites/all/modules/contrib/clickpath/clickpath.admin.inc
@@ -0,0 +1,62 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Admin settings form.
+ */
+
+/**
+ * Form with administrative settings for clickpath.
+ */
+function clickpath_admin_settings() {
+  $form = array();
+  $form['clickpath_count'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Number of paths to save'),
+    '#default_value' => variable_get('clickpath_count', 5),
+  );
+  $form['clickpath_title_length'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Limit titles to a specific length'),
+    '#default_value' => variable_get('clickpath_title_length', 20),
+  );
+  $form['clickpath_ignore_list'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Paths to ignore'),
+    '#default_value' => variable_get('clickpath_ignore_list', "admin*\nnode/*/*"),
+    '#description' => t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>')),
+  );
+  $form['clickpath_reverse_ignore'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Reverse ignore filter'),
+    '#default_value' => variable_get('clickpath_reverse_ignore', FALSE),
+    '#description' => t('Checking this box will reverse the ignore list above, thus only collecting the paths that match that pattern'),
+  );
+  $form['clickpath_home_title'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Title to display for frontpage'),
+    '#default_value' => variable_get('clickpath_home_title', 'Home'),
+    '#description' => t('The title entered here will be used to represent homepage. If nothing is entered, the title will default to site name.'),
+  );
+  $form['clickpath_breadcrumb'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Override breacrumb trail'),
+    '#default_value' => variable_get('clickpath_breadcrumb', FALSE),
+    '#description' => t('Replace the standard Drupal breadcrumb trail with a list of the most recent pages a user has visited. If this option is used, ignoring paths is NOT recommended.'),
+  );
+  $form['#validate'][] = 'clickpath_admin_settings_validate';
+  return system_settings_form($form);
+}
+
+/**
+ * Administrative settings validation.
+ */
+function clickpath_admin_settings_validate($form, &$form_state) {
+  if (!is_numeric($form_state['values']['clickpath_count'])) {
+    form_set_error('clickpath_count', t('Number of paths to save must be a positive number.'));
+  }
+  if (!empty($form_state['values']['clickpath_title_length']) && !is_numeric($form_state['values']['clickpath_title_length'])) {
+    form_set_error('clickpath_title_length', t('Title length must be a positive number.'));
+  }
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/clickpath/clickpath.module b/sites/all/modules/contrib/clickpath/clickpath.module
index dfd331e..3553f72 100644
--- a/sites/all/modules/contrib/clickpath/clickpath.module
+++ b/sites/all/modules/contrib/clickpath/clickpath.module
@@ -10,17 +10,48 @@
  */
 
 /**
- * Implementation of hook_perm.
- *
+ * Implementation of hook_init().
+ */
+function clickpath_init() {
+  // Adds our settings to the Drupal.settings js object.
+  drupal_add_js(array('clickpath' => clickpath_get_settings()), 'setting');
+}
+
+/**
+ * Implementation of hook_boot().
+ * Hook_boot is always called.
+ */
+function clickpath_boot() {
+  if (!isset($_SESSION['clickpath'])) {
+    $_SESSION['clickpath'] = clickpath_get_paths();
+  }
+}
+
+/**
+ * Implementation of hook_perm().
  * Exposes permission to see one's own clickpath.
  */
 function clickpath_perm() {
-  return array('view clickpath block');
+  return array(
+    'view clickpath block',
+    'administer clickpath',
+  );
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function clickpath_theme($existing, $type, $theme, $path) {
+  return array (
+    'clickpath_item' => array(
+      'variables' => array('item' => NULL),
+      'file' => 'clickpath.theme.inc',
+    ),
+  );
 }
 
 /**
  * Implementation of hook_menu.
- *
  * Exposes the Clickpath administration page.
  */
 function clickpath_menu() {
@@ -29,81 +60,18 @@ function clickpath_menu() {
     'description' => 'Configure what information will be displayed about the paths users take when clicking through the site.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('clickpath_admin_settings'),
-    'access arguments' => array('administer site configuration'),
+    'access arguments' => array('administer clickpath'),
     'type' => MENU_NORMAL_ITEM,
+    'file' => 'clickpath.admin.inc',
   );
 
   return $items;
 }
 
 /**
- * Form with administrative settings for clickpath.
- */
-function clickpath_admin_settings() {
-  $form = array();
-
-  $form['clickpath_count'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Number of paths to save'),
-    '#default_value' => variable_get('clickpath_count', 5),
-  );
-
-  $form['clickpath_title_length'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Limit titles to a specific length'),
-    '#default_value' => variable_get('clickpath_title_length', 20),
-  );
-
-  $description = t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>'));
-  $form['clickpath_ignore_list'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Paths to ignore'),
-    '#default_value' => variable_get('clickpath_ignore_list', "admin*\nnode/*/*"),
-    '#description' => $description,
-  );
-
-  $form['clickpath_reverse_ignore'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Reverse ignore filter'),
-    '#default_value' => variable_get('clickpath_reverse_ignore', FALSE),
-    '#description' => t('Checking this box will reverse the ignore list above, thus only collecting the paths that match that pattern'),
-  );
-
-  $form['clickpath_home_title'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Title to display for frontpage'),
-    '#default_value' => variable_get('clickpath_home_title', 'Home'),
-    '#description' => t('The title entered here will be used to represent homepage. If nothing is entered, the title will default to site name.'),
-  );
-
-  $form['clickpath_breadcrumb'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Override breacrumb trail'),
-    '#default_value' => variable_get('clickpath_breadcrumb', FALSE),
-    '#description' => t('Replace the standard Drupal breadcrumb trail with a list of the most recent pages a user has visited. If this option is used, ignoring paths is NOT recommended.'),
-  );
-
-  $form['#validate'][] = 'clickpath_admin_settings_validate';
-
-  return system_settings_form($form);
-}
-
-/**
- * Administrative settings validation.
- */
-function clickpath_admin_settings_validate($form, &$form_state) {
-  if (!is_numeric($form_state['values']['clickpath_count'])) {
-    form_set_error('clickpath_count', t('Number of paths to save must be a positive number.'));
-  }
-  if (!empty($form_state['values']['clickpath_title_length']) && !is_numeric($form_state['values']['clickpath_title_length'])) {
-    form_set_error('clickpath_title_length', t('Title length must be a positive number.'));
-  }
-}
-
-/**
  * Implementation of hook_block.
  *
- * Exposes a block containing the current user's most recently visited
+ * Exposes blocks containing the current user's most recently visited
  * pages -- the path they've taken through the site.
  */
 function clickpath_block($op = 'list', $delta = 0) {
@@ -113,39 +81,92 @@ function clickpath_block($op = 'list', $delta = 0) {
       'info' => t('Recently visited pages'),
       'cache' => BLOCK_NO_CACHE,
     );
+    $blocks['clickpath_by_type'] = array(
+      'info' => t('Recently visited pages by type'),
+      'cache' => BLOCK_NO_CACHE,
+    );
     return $blocks;
   }
-  else if ($op == 'view') {
-    if (user_access('view clickpath block') && $links = clickpath_get_paths()) {
-      $block = array();
-      $list = array();
-      $links = clickpath_get_paths();
-      $links = array_reverse($links, TRUE);
-      foreach ($links as $path => $title) {
-        $list[] = l(_clickpath_truncate_title($title), $path, array('html' => TRUE));
-      }
+  elseif ($op == 'view') {
+    $block = array(
+      'subject' => t('Recently visited pages'),
+      'content' => '',
+    );
+     if (!user_access('view clickpath block')) {
+      return $block;
+    }
+    $block = clickpath_render_block($delta);
+    return $block;
+  }
+}
 
-      $block['subject'] = t('Recently visited pages');
-      if (empty($list)) {
-        $block['content'] = t('You have not browsed any documents yet');
-      }
-      else {
-        $block['content'] = theme('item_list', $list);
+/**
+ * Processes the $stack to return a themed list of links for the block being
+ * rendered or an empty message is no items match the criteria.
+ * @param string $delta block identification
+ * @return string List of links or a message indicating that the stack is empty.
+ */
+function clickpath_render_block($delta) {
+  $list = array();
+  $links = array();
+  $type = (arg(0) ? arg(0) : 'other');
+
+  // Do block-specific adjustments
+  switch ($delta) {
+    case 'clickpath_by_type':
+      $stack = clickpath_get_paths('by_type');
+      $object = menu_get_object();
+      
+      if (isset($object->type)) {
+        $links = array_reverse($stack[$type][$object->type], TRUE);
       }
+      $content_type = ($object->type ? $object->type : 'document') . 's';
+      $subject = t('Recently viewed %content_type', array('%content_type' => $content_type));
+      break;
+    default:
+      $stack = clickpath_get_paths('all');
+      $links = array_reverse($stack, TRUE);
+      break;
+  }
 
-      return $block;
-    }
+  // Prepare empty message
+  $empty_message = t('You have not browsed any %content_type yet', array('%content_type' => $content_type));
+
+  // Theme our links
+  foreach ($links as $key => $item) {
+    $list[] = theme('clickpath_item', $item);
+  }
+
+  // Set the item list or the empty message
+  if (empty($list)) {
+    $content = $empty_message;
+  }
+  else {
+    $content = theme('item_list', $list);
   }
+
+  // Return the block object
+  return array(
+    'subject' => $subject,
+    'content' => $content,
+  );
 }
 
 /**
- * Function that replaces the default breadcrumb with clickpath's breadcrumb
- * if the settings call for it.
+ * Implementation of hook_preprocess_page().
+ *
+ * Keeps track of the pages visited. Moved in here from hook_exit, because in
+ * Pressflow that hook happens right after the session is commited and closed.
+ *
+ * Optionally it also replaces the default breadcrumb with it's own breadcrumb
  */
 function clickpath_preprocess_page(&$variables) {
+  // Save current page path to the stack
+  clickpath_save_path();
+
   if (variable_get('clickpath_breadcrumb', FALSE)) {
     $list = array();
-    $links = clickpath_get_paths();
+    $links = clickpath_get_paths('all');
     foreach ($links as $path => $title) {
       $list[] = l(_clickpath_truncate_title($title), $path, array('html' => TRUE));
     }
@@ -155,54 +176,158 @@ function clickpath_preprocess_page(&$variables) {
 }
 
 /**
- * Implementation of hook_exit().
- * Saves the path a user visited on page exit.
- */
-function clickpath_exit() {
-  clickpath_save_path($_GET['q']);
-}
-
-/**
  * Returns an array of visited pages.
  * An empty array is returned if no pages have been collected.
  * Array is stored in $_SESSION.
+ *
+ * Returns one of three things:
+ * $_SESSION['clickpath'][$key] when $key is defined and has contents
+ *
+ * @param <type> $key
+ * @param <type> $reset
+ * @return array Returns the value of $_SESSION['clickpath']
  */
-function clickpath_get_paths() {
-  return empty($_SESSION['clickpath']) ? array() : $_SESSION['clickpath'];
+function clickpath_get_paths($key = NULL, $reset = FALSE) {
+  if (!isset($_SESSION['clickpath']) || $reset) {
+    $_SESSION['clickpath'] = array(
+      'all' => array(),
+      'by_type' => array(),
+    );
+  }
+  if (isset($key)) {
+    if (!empty($_SESSION['clickpath'][$key])) {
+      return $_SESSION['clickpath'][$key];
+    }
+    else {
+      return FALSE;
+    }
+  }
+  return $_SESSION['clickpath'];
 }
 
 /**
- * Function that determines whether the current path is the frontpage. Boolean.
+ * Takes the current path and processes it for storage if valid.
+ * 
+ * @param string $path Optional $path. Defaults to $_GET['q'] when not present.
  */
-function _clickpath_path_is_frontpage($path) {
-  return $path == drupal_get_normal_path(variable_get('site_frontpage', 'node'));
+function clickpath_save_path($path = NULL) {
+  if ($path == NULL) {
+    $path = $_GET['q'];
+  }
+
+  $page_match = clickpath_page_match($path);
+
+  if ($page_match) {
+    // We will add type information to the session array so we can filter it later
+    $type = arg(0);
+    $object = menu_get_object($type, 1, $path);
+    if (_clickpath_path_is_frontpage($path)) {
+      if ($home_title = variable_get('clickpath_home_title', 'Home')) {
+        $title = $home_title;
+      }
+      else {
+        $title = variable_get('site_name', 'Home');
+      }
+    }
+    else {
+      $title = drupal_get_title();
+    }
+
+    // Load the stack
+    $stack = clickpath_get_paths();
+
+    // Prepare the item keyed by the path
+    $item = array(
+      'title' => $title,
+      'path' => $path,
+      'type' => $type,
+    );
+
+    // Append additional information based on the menu object type
+    switch ($type) {
+      case 'node':
+        $item['object'] = 'node';
+        $item['id'] = $object->nid;
+        break;
+      case 'user':
+        $item['object'] = 'user';
+        $item['id'] = $object->uid;
+        break;
+      default:
+        $item['object'] = 'page';
+        break;
+    }
+
+    if (!empty($object->type)) {
+      $item['type'] = $object->type;
+    }
+
+    // Notify other modules that we are collecting path information
+    // Modules implementing hook_clickpath_api($op, &$item = NULL, &$stack = NULL) should
+    // use $item to add values to the current item and $stack if you need to
+    // remove any of the other items already in the stack.
+    foreach (module_implements('clickpath_api') as $module) {
+      $function = $module . '_clickpath_api';
+      $function('save', $object, $item, $stack);
+    }
+    // Add the item to the stack
+    clickpath_add_to_stack($item, $stack);
+  }
 }
 
 /**
- * Function that truncates the maximum length of the titles to store.
+ * Saves and classifies an item into the stack
+ * @param array $item
+ * @param array $stack
  */
-function _clickpath_truncate_title($title) {
-  $length = variable_get('clickpath_title_length', 20);
-  $title = strip_tags($title);
-  if ($length > 0 && strlen($title) > $length) {
-    $title = substr($title, 0, $length) . '&hellip;';
+function clickpath_add_to_stack(array $item, array &$stack = NULL) {
+  // How many items should we show
+  $clickpath_count = (int)variable_get('clickpath_count', 5);
+
+  if (!isset($stack)) {
+    $stack = clickpath_get_paths();
   }
-  return $title;
+
+  // Retrieve the items from the stack
+  $all_paths = (array)$stack['all'];
+  $by_type = (array)$stack['by_type'][$item['object']][($item['type'] ? $item['type'] : 'other')];
+  
+  // Organize the item into our stack
+  $keyed_item = array($item['path'] => $item);
+
+  // Push the item onto the end of our stack
+  $all_paths[$item['path']] = $item;
+  if (count(array_keys($all_paths)) > $clickpath_count) {
+    $all_paths = array_slice($all_paths, 0, $clickpath_count, TRUE);
+  }
+
+  // Push the item onto the end of our stack
+  $by_type[$item['path']] = $item;
+  if (count(array_keys($by_type)) > $clickpath_count) {
+    $by_type = array_slice($by_type, 0, $clickpath_count, TRUE);
+  }
+
+  // Put our organized items back into the stack
+  $stack['all'] = $all_paths;
+  $stack['by_type'][$item['object']][$item['type']] = $by_type;
+
+  // Save to the session object
+  $_SESSION['clickpath'] = $stack;
 }
 
 /**
- * Function that takes the current path and processes it for storage if valid.
+ * Checks to see if the given path matches our pattern
+ * @param string $path
  */
-function clickpath_save_path($path = NULL) {
-  if ($path == NULL) {
-    return FALSE;
-  }
+function clickpath_page_match($path) {
   $path_pattern = variable_get('clickpath_ignore_list', "admin*\nnode/*/*");
   $reverse_pattern = (bool)variable_get('clickpath_reverse_ignore', FALSE);
 
-  // Match path if necessary
+  // Match path if we have a pattern to compare our path to.
   if (!empty($path_pattern)) {
-    $aliased_path = drupal_get_path_alias($path);
+    if (function_exists('drupal_get_path_alias')) {
+      $aliased_path = drupal_get_path_alias($path);
+    }
     // Compare with the internal and path alias (if any).
     $page_match = drupal_match_path($aliased_path, $path_pattern);
     if ($aliased_path != $_GET['q']) {
@@ -216,32 +341,50 @@ function clickpath_save_path($path = NULL) {
     $page_match = TRUE;
   }
 
-    if ($reverse_pattern == TRUE) {
-      $page_match = !$page_match;
-    }
+  if ($reverse_pattern == TRUE) {
+    $page_match = !$page_match;
+  }
+  return $page_match;
+}
 
-  if ($page_match) {
-    if (_clickpath_path_is_frontpage($path)) {
-      if ($home_title = variable_get('clickpath_home_title', 'Home')) {
-        $title = $home_title;
-      } else {
-        $title = variable_get('site_name', 'Home');
-      }
-    } else {
-      $title = drupal_get_title();
-    }
+/**
+ * Determines whether the current path is the frontpage. Boolean.
+ */
+function _clickpath_path_is_frontpage($path) {
+  return $path == drupal_get_normal_path(variable_get('site_frontpage', 'node'));
+}
 
-    $clickpath = clickpath_get_paths();
-    if (empty($clickpath[$path])) {
-      $clickpath[$path] = $title;
-      while (count($clickpath) > variable_get('clickpath_count', 5)) {
-        array_shift($clickpath);
-      }
-    }
-    else {
-      unset($clickpath[$path]);
-      $clickpath[$path] = $title;
-    }
-    $_SESSION['clickpath'] = $clickpath;
+/**
+ * Truncates the maximum length of the titles to store.
+ */
+function _clickpath_truncate_title($title) {
+  $length = variable_get('clickpath_title_length', 20);
+  $title = strip_tags($title);
+  if ($length > 0 && strlen($title) > $length) {
+    $title = substr($title, 0, $length) . '&hellip;';
+  }
+  return $title;
+}
+
+/**
+ * Retrieve clickpath settings
+ */
+function clickpath_get_settings() {
+  $settings = array();
+
+  // Retrieve our settings and defaults
+  $settings['clickpath_count'] = (int)variable_get('clickpath_count', 5);
+  $settings['clickpath_title_length'] = (int)variable_get('clickpath_title_length', 20);
+  $settings['clickpath_ignore_list'] = variable_get('clickpath_ignore_list', "admin*\nnode/*/*");
+  $settings['clickpath_reverse_ignore'] = (bool)variable_get('clickpath_reverse_ignore', FALSE);
+  $settings['clickpath_home_title'] = variable_get('clickpath_home_title', 'Home');
+  $settings['clickpath_breadcrumb'] = (bool)variable_get('clickpath_breadcrumb', FALSE);
+
+  // Allow other modules to extend this list if they need to
+  foreach (module_implements('clickpath_settings') as $module) {
+    $function = $module . '_clickpath_settings';
+    $function($settings);
   }
+
+  return $settings;
 }
\ No newline at end of file
diff --git a/sites/all/modules/contrib/clickpath/clickpath.theme.inc b/sites/all/modules/contrib/clickpath/clickpath.theme.inc
new file mode 100644
index 0000000..4416f38
--- /dev/null
+++ b/sites/all/modules/contrib/clickpath/clickpath.theme.inc
@@ -0,0 +1,16 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Theme functions
+ */
+
+/**
+ * Theme clickpath items
+ * @param array $item
+ * @return string Themed item
+ */
+function theme_clickpath_item($item) {
+  return l(_clickpath_truncate_title($item['title']), $item['path'], array('html' => TRUE));
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.info b/sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.info
new file mode 100644
index 0000000..34e07aa
--- /dev/null
+++ b/sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.info
@@ -0,0 +1,6 @@
+; $Id$
+name = Clickpath CCK
+description = "Extends clickpath to handle user configurable cck fields"
+core = 6.x
+dependencies[] = clickpath
+project = "clickpath_cck"
\ No newline at end of file
diff --git a/sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.module b/sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.module
new file mode 100644
index 0000000..50ccffa
--- /dev/null
+++ b/sites/all/modules/contrib/clickpath/clickpath_cck/clickpath_cck.module
@@ -0,0 +1,136 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Extends clickpath allowing it to track cck info that you want to have
+ * available when you display the block.
+ */
+
+/**
+ * Implementation of hook_clickpath_api().
+ *
+ * @param string $op Action being performed.
+ * @param array $item The path currently being added to the stack.
+ * @param array $stack The entire stack so far.
+ */
+function clickpath_cck_clickpath_api($op, $object, &$item, &$stack) {
+  switch ($op) {
+    case 'save':
+      foreach ($object as $fieldname => $field) {
+        if (!in_array($fieldname, $clickpath_cck_fields)) {
+          continue;
+        }
+        if (is_array($field)) {
+          foreach ($field as $key => $instance) {
+            $item['cck'][$fieldname][] = clickpath_cck_get_field_value($instance);
+          }
+        }
+        else {
+          $item['cck'][$fieldname][] = clickpath_cck_get_field_value($instance);
+        }
+      }
+      break;
+  }
+}
+
+/**
+ * Helper function that selects the best value from a cck field array
+ * @param <type> $field
+ * @return <type>
+ */
+function clickpath_cck_get_field_value($field) {
+  if (!empty($field['safe'])) {
+    return $field['safe'];
+  }
+  elseif (!empty($field['view'])) {
+    return $field_value = $field['view'];
+  }
+  elseif (!empty($field['value'])) {
+    return $field_value = $field['value'];
+  }
+}
+
+/**
+ * Implementation of hook_clickpath_settings().
+ */
+function clickpath_cck_clickpath_settings(&$settings) {
+  $settings['clickpath_cck_fields'] = clickpath_cck_get_fields();
+}
+
+/**
+ * Implementation of hook_form_FORM_ID_alter().
+ *
+ * Adds cck selection settings to the administrative settings form for clickpath
+ */
+function clickpath_cck_form_clickpath_admin_settings_alter(&$form, &$form_state) {
+  // Prepare the $cck_fields array
+  foreach (content_fields() as $field) {
+    $cck_fields[$field['field_name']] = t('!fieldname !description', array(
+      '!fieldname' => $field['field_name'],
+      '!description' => (!empty($field['widget']['label']) ? '(' . $field['widget']['label'] . ')' : ''),
+    ));
+  }
+
+  // Sort the array by key.
+  ksort($cck_fields);
+
+  // Add our form fields
+  $form['clickpath_cck'] = array(
+    '#id' => 'clickpath_cck',
+    '#type' => 'fieldset',
+    '#title' => t('Content Field Tracking'),
+    '#description' => t('By default, all fields are tracked. You can use this
+      section to define specific fields that you want to track. If you wish to
+      track none, you can turn off this module'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#weight' => -2,
+  );
+  $form['clickpath_cck']['clickpath_cck_fields'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Content fields to be tracked'),
+    '#options' => $cck_fields,
+    '#default_value' => clickpath_cck_get_fields(),
+    '#element_validate' => array('clickpath_cck_settings_validate'),
+  );
+  if (module_exists('checkall')) {
+    $form['clickpath_cck']['clickpath_cck_fields']['#checkall'] = TRUE;
+  }
+}
+
+/**
+ * Remove empty values from the comparable_types array.
+ */
+function clickpath_cck_settings_validate($element, &$form_state) {
+  if (is_array($form_state['values']['clickpath_cck_fields'])) {
+    $options = array();
+    foreach (array_filter($form_state['values']['clickpath_cck_fields']) as $field) {
+      $options[$field] = $field;
+    }
+    $form_state['values']['clickpath_cck_fields'] = $options;
+  }
+}
+
+/**
+ * Helper function that retrieves the fields to be stored and displayed in the
+ * block.
+ *
+ * @return array
+ */
+function clickpath_cck_get_fields() {
+  // Retrieve the variable
+  $cck_fields = variable_get('clickpath_cck_fields', array());
+
+  // If we don't have any specific cck fields defined, return all of them.
+  if (empty($cck_fields)) {
+    foreach (content_fields() as $field) {
+      $cck_fields[$field['field_name']] = $field['field_name'];
+    }
+  }
+
+  // Sort the array by key.
+  ksort($cck_fields);
+
+  return $cck_fields;
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.info b/sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.info
new file mode 100644
index 0000000..948622e
--- /dev/null
+++ b/sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.info
@@ -0,0 +1,6 @@
+; $Id$
+name = Clickpath Taxonomy
+description = "Extends clickpath to handle taxonomy information"
+core = 6.x
+dependencies[] = clickpath
+project = "clickpath_taxonomy"
\ No newline at end of file
diff --git a/sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.module b/sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.module
new file mode 100644
index 0000000..6b88e03
--- /dev/null
+++ b/sites/all/modules/contrib/clickpath/clickpath_taxonomy/clickpath_taxonomy.module
@@ -0,0 +1,24 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Extends clickpath allowing it to track taxonomy info that you want to have
+ * available when you display the block.
+ */
+
+/**
+ * Implementation of hook_clickpath_api().
+ *
+ * @param string $op
+ * @param object $object
+ * @param array $item
+ * @param array $stack
+ */
+function clickpath_taxonomy_clickpath_api($op, $object, &$item, &$stack) {
+  switch ($op) {
+    case 'save':
+      // @TODO: Add
+      break;
+  }
+}
\ No newline at end of file
diff --git a/sites/all/modules/contrib/clickpath/js/clickpath.admin.js b/sites/all/modules/contrib/clickpath/js/clickpath.admin.js
new file mode 100644
index 0000000..71a2fcb
--- /dev/null
+++ b/sites/all/modules/contrib/clickpath/js/clickpath.admin.js
@@ -0,0 +1,55 @@
+Drupal.behaviors.clickPath = function (context) {
+  $('fieldset#clickpath_cck:not(.clickpath_cck_processed)', context).each(Drupal.clickPath);
+};
+
+Drupal.clickPath = function() {
+  // Do not add a "Select all" checkbox if there are no rows with checkboxes in the fieldset
+  if ($('div.form-checkboxes input:checkbox', this).size() == 0) {
+    return;
+  }
+
+  // Keep track of the fieldset, which checkbox is checked and alias the settings.
+  var fieldset = this, checkboxes, lastChecked;
+  var strings = { 'selectAll': Drupal.t('Select all'), 'selectNone': Drupal.t('Deselect all') };
+  var updateSelectAll = function(state) {
+    $('div.form-checkboxes input:checkbox', fieldset).each(function() {
+      $(this).attr('value', state ? strings.selectNone : strings.selectAll);
+      this.checked = state;
+    });
+  };
+
+  // Find all <th> with class select-all, and insert the check all checkbox.
+  $('div.form-checkboxes', fieldset).prepend($('<label for="select-all-fields"><input type="checkbox" id="select-all-fields" class="form-checkbox select-all" /><span>&nbsp;</span></label>').find('span').val(strings.selectAll)).click(function(event) {
+    if ($(event.target).is('input:checkbox')) {
+      // Loop through all checkboxes and set their state to the select all checkbox' state.
+      checkboxes.each(function() {
+        this.checked = event.target.checked;
+        // Either add or remove the selected class based on the state of the check all checkbox.
+        $(this).parents('label')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
+      });
+      // Update the title and the state of the check all box.
+      updateSelectAll(event.target.checked);
+    }
+  });
+
+  // For each of the checkboxes within the fieldset.
+  checkboxes = $('div.form-checkboxes div.form-item', fieldset).click(function(e) {
+    // Either add or remove the selected class based on the state of the check all checkbox.
+    $(this).parents('div.form-item')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
+
+    // If this is a shift click, we need to highlight everything in the range.
+    // Also make sure that we are actually checking checkboxes over a range and
+    // that a checkbox has been checked or unchecked before.
+    if (e.shiftKey && lastChecked && lastChecked != e.target) {
+      // We use the checkbox's parent TR to do our range searching.
+      Drupal.clickPathRange($(e.target).parents('div.form-item')[0], $(lastChecked).parents('div.form-item')[0], e.target.checked);
+    }
+
+    // If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
+    updateSelectAll((checkboxes.length == $(checkboxes).filter(':checked').length));
+
+    // Keep track of the last checked checkbox.
+    lastChecked = e.target;
+  });
+  $(this).addClass('clickpath_cck_processed');
+};
\ No newline at end of file
-- 
1.7.2

