? _project.inc_
? _project.module_
? test.patch
? release/history
Index: usage/project_usage.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/usage/project_usage.install,v
retrieving revision 1.1
diff -u -p -u -r1.1 project_usage.install
--- usage/project_usage.install	7 Aug 2007 20:21:33 -0000	1.1
+++ usage/project_usage.install	15 Aug 2007 18:28:27 -0000
@@ -38,6 +38,15 @@ function project_usage_install() {
           count int unsigned NOT NULL default '0',
           PRIMARY KEY (nid, timestamp)
         ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      db_query("CREATE TABLE IF NOT EXISTS {cache_project_usage} (
+          cid varchar(255) BINARY NOT NULL default '',
+          data longblob,
+          expire int NOT NULL default '0',
+          created int NOT NULL default '0',
+          headers text,
+          PRIMARY KEY (cid),
+          INDEX expire (expire)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */;");
       break;
   }
 }
@@ -48,6 +57,7 @@ function project_usage_uninstall() {
     'project_usage_day',
     'project_usage_week_project',
     'project_usage_week_release',
+    'cache_project_usage',
   );
   foreach ($tables as $table) {
     if (db_table_exists($table)) {
@@ -61,8 +71,32 @@ function project_usage_uninstall() {
     'project_usage_life_daily',
     'project_usage_life_weekly_project',
     'project_usage_life_weekly_release',
+    'project_usage_date_long',
+    'project_usage_date_short',
   );
   foreach ($variables as $variable) {
     variable_del($variable);
   }
 }
+
+/**
+ * Add a cache table {cache_project_release}.
+ */
+function project_usage_update_5000() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("CREATE TABLE IF NOT EXISTS {cache_project_usage} (
+        cid varchar(255) BINARY NOT NULL default '',
+        data longblob,
+        expire int NOT NULL default '0',
+        created int NOT NULL default '0',
+        headers text,
+        PRIMARY KEY (cid),
+        INDEX expire (expire)
+      ) /*!40100 DEFAULT CHARACTER SET UTF8 */;");
+      break;
+  }
+  return $ret;
+}
Index: usage/project_usage.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/usage/project_usage.module,v
retrieving revision 1.4
diff -u -p -u -r1.4 project_usage.module
--- usage/project_usage.module	14 Aug 2007 00:51:53 -0000	1.4
+++ usage/project_usage.module	15 Aug 2007 19:57:55 -0000
@@ -29,6 +29,13 @@ define('PROJECT_USAGE_WEEK', PROJECT_USA
 // Number of seconds in a year.
 define('PROJECT_USAGE_YEAR', PROJECT_USAGE_DAY * 365);
 
+// Date formats for month and day. We define our own rather than using core's 
+// 'date_format_short' and 'date_format_long' variables because our timestamps
+// don't have hour or minute resolution so displaying that would be confusing
+// and take up extra space.
+define('PROJECT_USAGE_DATE_LONG', variable_get('project_usage_date_long', 'F jS'));
+define('PROJECT_USAGE_DATE_SHORT', variable_get('project_usage_date_short', 'M j'));
+
 /**
  * Implementation of hook_menu().
  */
@@ -43,11 +50,228 @@ function project_usage_menu($may_cache) 
       'description' => t('Configure how long usage data is retained.'),
       'weight' => 1,
     );
+    $items[] = array(
+      'path' => 'project-usage',
+      'title' => t('Project usage'),
+      'callback' => 'project_usage_overview',
+      'access' => user_access('view project usage'),
+    );
+  }
+  else {
+    // Project and release usage tabs.
+    if (arg(0) == 'node' && is_numeric(arg(1))) {
+      $node = node_load(arg(1));
+      if ($node->nid && ($node->type == 'project_project' || $node->type == 'project_release')) {
+        $items[] = array(
+          'path' => 'node/'. arg(1) .'/usage',
+          'title' => t('Usage'),
+          'access' => user_access('view project usage'),
+          'callback' => ($node->type == 'project_project') ? 'project_usage_project_page' : 'project_usage_release_page',
+          'callback arguments' => array($node),
+          'type' => MENU_LOCAL_TASK,
+          'weight' => 2,
+        );
+      }
+    }
   }
   return $items;
 }
 
 /**
+ * Implementation of hook_help().
+ */
+function project_usage_help($section) {
+  switch ($section) {
+    case 'project-usage':
+      return t('The following is a summary of the usage information for the projects on this site. The count is the total number of sites using any version of the project. Only sites that have opted to allow usage information to be tracked are included.');
+
+    case 'admin/modules#description':
+      return t('Collects and processes usage information about projects and releases.');
+  }
+} 
+
+/**
+ * Implementation of hook_perm().
+ */
+function project_usage_perm() {
+  $perms = array(
+    'view project usage',
+  );
+  return $perms;
+}
+
+/**
+ * Display and overview of usage for all modules.
+ */
+function project_usage_overview() {
+  drupal_set_title(t('Project usage overview'));
+
+  $week_count = 6;
+  $weeks = project_usage_get_weeks_since(time() - ($week_count * PROJECT_USAGE_WEEK));
+  // array_pop of the current week, since we won't have usage data for that,
+  // then reverse the order so it's newest to oldest.
+  array_pop($weeks);
+  $weeks = array_reverse($weeks);
+
+  // In order to get the project usage data into a sortable table, we need to
+  // build a query that returns all the projects in {node} table LEFT JOINed
+  // to the {project_usage_week_project} table for each week of usage that
+  // we're going to list. We have to use the LEFT JOIN, rather than the more
+  // efficient INNER JOIN, because some weeks may not have usage data which
+  // would cause the row to be omitted.
+  $header = array(array('field' => 'title', 'data' => t('Project'), 'sort' => 'asc'));
+  $fields = array('n.nid', 'n.title');
+  $joins = array();
+  foreach ($weeks as $i => $week) {
+    $header[] = array(
+      'field' => "week{$i}",
+      'data' => format_date($week, 'custom', PROJECT_USAGE_DATE_SHORT),
+    );
+    $fields[] = "p{$i}.count AS week{$i}";
+    $joins[] = "LEFT JOIN {project_usage_week_project} p{$i} ON n.nid = p{$i}.nid AND p{$i}.timestamp = $week";
+  }
+
+
+  // Check for a cached page. The cache id needs to take into account the page
+  // and sort column and order.
+  $page = isset($_GET['page']) ? (int) $_GET['page'] : 0;
+  $sort = tablesort_init($header);
+  $cid = 'overview:'. $page .':'. $sort['sql'] .':'. $sort['sort'];
+  if ($cached = cache_get($cid, 'cache_project_usage')) {
+    return $cached->data;
+  }
+
+
+  // Fire off all those LEFT JOINs... you can almost hear the DB crying.
+  $result = pager_query('SELECT '. implode(', ', $fields) .' FROM {node} n '
+    . implode(' ', $joins) .' WHERE n.nid IN (SELECT nid FROM {project_usage_week_project})'
+    . tablesort_sql($header), 30, 0);
+  // Take the rows and force any NULL counts to zero. 
+  while ($line = db_fetch_array($result)) {
+    $row = array(l($line['title'], 'node/'. $line['nid'] .'/usage'));
+    for ($i = 0; $i < $week_count; $i++) {
+      $row[] = (int) $line["week{$i}"];
+    }
+    $rows[] = $row;
+  }
+  $output = theme('table', $header, $rows) . theme('pager', NULL, 30, 0);
+
+
+  // Cache the completed page.
+  cache_set($cid, 'cache_project_usage', $output);
+  return $output;
+}
+
+/**
+ * Display the usage history of a project node.
+ */
+function project_usage_project_page($node) {
+  // Setup the bread crumbs.
+  project_project_set_breadcrumb($node, $breadcrumb);
+  drupal_set_title(check_plain($node->title));
+
+
+  // Check for a cached page.
+  $cid = "project:{$node->nid}";
+  if ($cached = cache_get($cid, 'cache_project_usage')) {
+    return $cached->data;
+  }
+
+
+  // Build a table of the weekly usage data with a column for each API version.
+  $output .= '<h3>'. t('Weekly project usage') .'</h3>';
+
+  // Get an array of the weeks going back as far as we have data...
+  $oldest = db_result(db_query("SELECT MIN(timestamp) FROM {project_usage_week_project} WHERE nid = %d", $node->nid));
+  $weeks = project_usage_get_weeks_since($oldest);
+  // ...ignore the current week, since we won't have usage data for that and
+  // reverse the order so it's newest to oldest.
+  array_pop($weeks);
+  $weeks = array_reverse($weeks);
+
+  // The number of columns varies depending on how many different API versions
+  // are in use. Set up the header and a blank, template row, based on the
+  // number of distinct terms in use.
+  $header = array(t('Week'));
+  $blank_row = array();
+  $result = db_query("SELECT DISTINCT td.tid, td.name FROM {project_usage_week_project} p INNER JOIN {term_data} td ON p.tid = td.tid WHERE p.nid = %d ORDER BY td.weight, td.name", $node->nid);
+  while ($row = db_fetch_object($result)) {
+    $header[$row->tid] = $row->name;
+    $blank_row[$row->tid] = 0;
+  }
+
+  // Now create a blank table with a row for each week and formatted date in
+  // the first column...
+  $rows = array();
+  foreach ($weeks as $week) {
+    $rows[$week] = array(0 => format_date($week, 'custom', PROJECT_USAGE_DATE_LONG)) + $blank_row;
+  }
+  // ...then fill it in with our data.
+  $result = db_query("SELECT timestamp, tid, count FROM {project_usage_week_project} WHERE nid = %d", $node->nid);
+  while ($row = db_fetch_object($result)) {
+    $rows[$row->timestamp][$row->tid] = $row->count;
+  }
+  // Strip out the keys so that the odd/even row themeing works correctly.
+  $output .= theme('table', $header, array_values($rows));
+
+
+
+  // Now build a table showing this week's usage for each release.
+  $output .= '<h3>'. t("Previous week's release usage") .'</h3>';
+
+  $header = array(t('Release'), t('Count'));
+  $rows = array();
+  $releases = project_release_get_releases($node, FALSE);
+  $result = db_query('SELECT n.nid, p.count FROM {node} n LEFT JOIN {project_usage_week_release} p ON n.nid = p.nid AND p.timestamp = %d WHERE n.nid IN ('. implode(',', array_keys($releases)) .') ORDER BY n.title DESC', $weeks[0]);
+  while ($line = db_fetch_array($result)) {
+    $rows[] = array(
+      l($releases[$line['nid']], 'node/'. $line['nid'] .'/usage'),
+      (int) $line['count'],
+    );
+  }
+  $output .= theme('table', $header, $rows);
+
+
+  // Cache the completed page.
+  cache_set($cid, 'cache_project_usage', $output);
+  return $output;
+}
+
+/**
+ * Display the usage history of a release node.
+ */
+function project_usage_release_page($node) {
+  // Setup the bread crumbs.
+  $project = node_load($node->pid);
+  $breadcrumb[] = l($project->title, 'node/'. $project->nid);
+  $breadcrumb[] = l(t('Releases'), 'node/'. $project->nid .'/release');
+  project_project_set_breadcrumb($project, $breadcrumb);
+  drupal_set_title(check_plain($node->title));
+
+
+  // Check for a cached page.
+  $cid = "release:{$node->nid}";
+  if ($cached = cache_get($cid, 'cache_project_usage')) {
+    return $cached->data;
+  }
+
+
+  // Table displaying the usage back through time.
+  $header = array(t('Week starting'), t('Count'));
+  $rows = array();
+  $query = db_query("SELECT timestamp, count FROM {project_usage_week_release} WHERE nid = %d ORDER BY timestamp DESC", $node->nid);
+  while ($row = db_fetch_object($query)) {
+    $rows[] = array(format_date($row->timestamp, 'custom', PROJECT_USAGE_DATE_LONG), $row->count);
+  }
+  $output = theme('table', $header, $rows);
+
+
+  // Cache the completed page.
+  cache_set($cid, 'cache_project_usage', $output);
+  return $output;
+}
+
+/**
  * Module settings form.
  */
 function project_usage_settings_form() {
@@ -111,7 +335,7 @@ function project_usage_cron() {
 
 /**
  * Process all the raw data up to the previous day. 
- * 
+ *
  * The primary key on the {project_usage_raw} table will prevent duplicate
  * records provided we process them once the day is complete. If we pull them
  * out too soon and the site checks in again they will be counted twice.
@@ -123,6 +347,7 @@ function project_usage_process_daily() {
 
   watchdog('project_usage', t('Starting to process daily usage data for @date.', array('@date' => format_date($timestamp))));
 
+
   // Assign API version term IDs.
   $terms = array();
   foreach (project_release_get_api_taxonomy() as $term) {
@@ -135,6 +360,7 @@ function project_usage_process_daily() {
   }
   watchdog('project_usage', t('Assigned API version term IDs.'));
 
+
   // Asign project and release node IDs.
   $query = db_query("SELECT DISTINCT project_uri, project_version FROM {project_usage_raw} WHERE pid = 0 OR nid = 0");
   while ($row = db_fetch_object($query)) {
@@ -146,17 +372,20 @@ function project_usage_process_daily() {
   }
   watchdog('project_usage', t('Assigned project and release node IDs.'));
 
+
   // Move usage records with project node IDs into the daily table and remove
   // the rest.
   db_query("INSERT INTO {project_usage_day} (timestamp, site_key, pid, nid, tid) SELECT timestamp, site_key, pid, nid, tid FROM {project_usage_raw} WHERE timestamp < %d AND pid <> 0", $timestamp);
   db_query("DELETE FROM {project_usage_raw} WHERE timestamp < %d", $timestamp);
   watchdog('project_usage', t('Moved usage from raw to daily.'));
 
+
   // Remove old daily records.
   $seconds = variable_get('project_usage_life_daily', 4 * PROJECT_USAGE_WEEK);
   db_query("DELETE FROM {project_usage_day} WHERE timestamp < %d", time() - $seconds);
   watchdog('project_usage', t('Removed old daily rows.'));
 
+
   watchdog('project_usage', t('Completed daily usage data processing.'));
 }
 
@@ -168,7 +397,8 @@ function project_usage_process_daily() {
  */
 function project_usage_process_weekly($timestamp) {
   watchdog('project_usage', t('Starting to process weekly usage data.'));
-  
+
+
   // Get all the weeks since we last ran.
   $weeks = project_usage_get_weeks_since($timestamp);
   // Skip the last entry since it's the current, incomplete week.
@@ -187,6 +417,7 @@ function project_usage_process_weekly($t
     watchdog('project_usage', t('Computed weekly release tallies for @date.', array('@date' => format_date($start))));
   }
 
+
   // Remove any tallies that have aged out.
   $now = time();
   $project_life = variable_get('project_usage_life_weekly_project', PROJECT_USAGE_YEAR); 
@@ -197,6 +428,12 @@ function project_usage_process_weekly($t
   db_query("DELETE FROM {project_usage_week_release} WHERE timestamp < %d", $now - $release_life);
   watchdog('project_usage', t('Removed old weekly release rows.'));
 
+
+  // Clear out the cached usage pages.
+  cache_clear_all('*', 'cache_project_usage', TRUE);
+  watchdog('project_usage', t('Cleared project usage cache.'));
+
+
   watchdog('project_usage', t('Completed weekly usage data processing.'));
 }
 
