Index: ad.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/Attic/ad.info,v
retrieving revision 1.1.2.2.2.1
diff -u -r1.1.2.2.2.1 ad.info
--- ad.info	11 Aug 2008 21:15:26 -0000	1.1.2.2.2.1
+++ ad.info	20 Nov 2008 00:00:38 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = taxonomy
 description = An advertising system for Drupal powered websites.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: ad.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/ad.install,v
retrieving revision 1.2.2.4.2.27.2.2
diff -u -r1.2.2.4.2.27.2.2 ad.install
--- ad.install	19 Nov 2008 17:38:36 -0000	1.2.2.4.2.27.2.2
+++ ad.install	27 Jan 2009 13:51:55 -0000
@@ -2,241 +2,336 @@
 // $Id: ad.install,v 1.2.2.4.2.27.2.2 2008/11/19 17:38:36 jeremy Exp $
 
 /**
+ * @file
+ * advertisement module database schema.
+ */
+
+/**
  * Ad module database schema.
- * Copyright (c) 2005-2008 Jeremy Andrews <jeremy@kerneltrap.org>.  
  */
+function ad_schema() {
+ /**
+  * The ad table stores administrative information about each ad.  The
+  * actual ad itself can be found in the appropriate ad type table.
+  */
+  $schema['ads'] = array(
+    'description' => 'The ad table stores administrative information about each ad.  The actual ad itself can be found in the appropriate ad type table.',
+    'fields' => array(
+      'aid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Unique ad ID. Equals to ad nid.',
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'The {users}.uid that owns this node; initially, this is the user that created it.',
+      ),
+      'adstatus' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Ad status',
+      ),
+      'adtype' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Ad type',
+      ),
+      'redirect' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Ad redirect URL',
+      ),
+      'autoactivate' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Is ad autoactivating?',
+      ),
+      'autoactivated' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Is ad autoactivated?',
+      ),
+      'autoexpire' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Is ad autoexpiring?',
+      ),
+      'autoexpired' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Is ad autoexpired?',
+      ),
+      'activated' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Is ad activated?',
+      ),
+      'maxviews' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Maximum ad views',
+      ),
+      'maxclicks' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Maximum ad clicks',
+      ),
+      'expired' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Is ad expired?',
+      ),
+    ),
+    'primary key' => array('aid'),
+    'indexes' => array(
+      'uid' => array('uid'),
+      'autoactivate' => array('autoactivate'),
+      'autoactivate' => array('autoactivate'),
+    ),
+  );
+
+ /**
+  * This table counts each time a given action occurs on an ad.  Actions
+  * include when the ad is viewed, clicked, enabled and disabled.
+  * Statistics are collected at an hourly granularity.
+  *
+  * The source column is used for tracking statistics for externally
+  * hosted ads.
+  *
+  * Actions:
+  *  'view', 'click', 'enable', 'disable'
+  */
+  $schema['ad_statistics'] = array(
+    'description' => 'Stores ad statistics.',
+    'fields' => array(
+      'sid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'description' => 'Statistics entry ID.',
+      ),
+      'aid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Ad id.',
+      ),
+      'date' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Date when action was made.',
+      ),
+      'action' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Actions: "view", "click", "enable", "disable".',
+      ),
+      'adgroup' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'default' => '',
+        'description' => 'Ad group.',
+      ),
+      'hostid' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Host from which acion was made.',
+      ),
+      'count' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Count of actions triggered.',
+      ),
+    ),
+    'primary key' => array('sid'),
+    'indexes' => array(
+      'aid' => array('aid'),
+      'date' => array('date'),
+      'action' => array('action'),
+      'adgroup' => array('adgroup'),
+      'hostid' => array('hostid'),
+    ),
+  );
+
+ /**
+  * The ad_clicks table tracks when a given advertisement was clicked,
+  * who clicked it (uid if any and IP address), and what page they were
+  * on when they clicked it.
+  */
+  $schema['ad_clicks'] = array(
+    'description' => 'The ad_clicks table tracks when a given advertisement was clicked, who clicked it (uid if any and IP address), and what page they were on when they clicked it.',
+    'fields' => array(
+      'cid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'description' => 'Statistics entry ID.',
+      ),
+      'aid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Ad id.',
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => '',
+      ),
+      'status' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => '',
+      ),
+      'hostname' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Host from which acion was made.',
+      ),
+      'user_agent' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Clicker\'s browser agent.',
+      ),
+      'adgroup' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Ad group.',
+      ),
+      'hostid' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Host from which acion was made.',
+      ),
+      'url' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'default' => '',
+        'description' => 'Clicked URL.',
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Date when action was made.',
+      ),
+    ),
+    'primary key' => array('cid'),
+    'indexes' => array(
+      'aid' => array('aid'),
+      'status' => array('status'),
+      'hostname' => array('hostname'),
+      'user_agent' => array('user_agent'),
+      'adgroup' => array('adgroup'),
+      'hostid' => array('hostid'),
+      'url' => array('url'),
+    ),
+  );
+
+ /**
+  * The ad_hosts table is used to configure users that can display ads
+  * remotely.
+  */
+  $schema['ad_hosts'] = array(
+    'description' => 'The ad_hosts table is used to configure users that can display ads remotely. ',
+    'fields' => array(
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => '',
+      ),
+      'hostid' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Host from which acion was made.',
+      ),
+      'status' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => '',
+      ),
+      'description' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'description' => 'Host from which acion was made.',
+      ),
+    ),
+    'primary key' => array('uid'),
+    'indexes' => array(
+      'status' => array('status'),
+      'hostid' => array('hostid'),
+    ),
+  );
 
-function ad_install() {
-  switch ($GLOBALS['db_type']) {
-    case 'pgsql':
-     /* The ad table stores administrative information about each ad.  The
-      * actual ad itself can be found in the appropriate ad type table.
-      */
-     db_query("CREATE TABLE {ads} (
-       aid INT NOT NULL UNIQUE DEFAULT '0' PRIMARY KEY,
-       uid INT NOT NULL DEFAULT '0',
- 
-       adstatus VARCHAR(255) NOT NULL DEFAULT '',
-       adtype VARCHAR(255) NOT NULL DEFAULT '',
-
-       redirect VARCHAR(255) NOT NULL DEFAULT '',
-
-       autoactivate INT NOT NULL DEFAULT '0',
-       autoactivated INT NOT NULL DEFAULT '0',
-       autoexpire INT NOT NULL DEFAULT '0',
-       autoexpired INT NOT NULL DEFAULT '0',
-
-       activated INT NOT NULL DEFAULT '0',
-       maxviews INT NOT NULL DEFAULT '0',
-       maxclicks INT NOT NULL DEFAULT '0',
-       expired INT NOT NULL DEFAULT '0'
-     );");
-
-     /**
-      * Every ad can have one or more owners.
-      */
-     db_query("CREATE TABLE {ad_owners} (
-       oid SERIAL NOT NULL PRIMARY KEY,
-       aid INT NOT NULL DEFAULT '0',
-       uid INT NOT NULL DEFAULT '0'
-     );");
-
-     /**
-      * Permissions can be granted to each owner of each ad.  The same owner
-      * can own multiple ads, and can have different permissions for each ad.
-      */
-     db_query("CREATE TABLE {ad_permissions} (
-       oid INT NOT NULL DEFAULT '0' PRIMARY KEY,
-       permissions TEXT NULL DEFAULT ''
-     );");
-
-     /**
-      * This table counts each time a given action occurs on an ad.  Actions
-      * include when the ad is viewed, clicked, enabled and disabled.
-      * Statistics are collected at an hourly granularity.
-      *
-      * The source column is used for tracking statistics for externally 
-      * hosted ads.
-      *
-      * Actions:
-      *  'view', 'click', 'enable', 'disable'
-      */
-     db_query("CREATE TABLE {ad_statistics} (
-       sid SERIAL NOT NULL PRIMARY KEY,
-       aid INT NOT NULL DEFAULT '0',
-
-       date INT NOT NULL DEFAULT '0',
-       action VARCHAR(255) NOT NULL DEFAULT '',
-       adgroup VARCHAR(255) NULL DEFAULT '',
-       hostid VARCHAR(32) NULL DEFAULT '',
-       count INT NOT NULL DEFAULT '0'
-     );");
-
-     /**
-      * The ad_clicks table tracks when a given advertisement was clicked, 
-      * who clicked it (uid if any and IP address), and what page they were
-      * on when they clicked it.
-      */
-      db_query("CREATE TABLE {ad_clicks} (
-        cid SERIAL NOT NULL PRIMARY KEY,
-        aid INT NOT NULL DEFAULT '0',
-        uid INT NOT NULL DEFAULT '0',
-
-        status INT NOT NULL DEFAULT '0',
- 
-        hostname varchar(128) NOT NULL DEFAULT '',
-        user_agent varchar(255) NOT NULL DEFAULT '',
-        adgroup varchar(255) NOT NULL DEFAULT '',
-        hostid varchar(32) NOT NULL DEFAULT '',
-        url varchar(255) DEFAULT '',
-        timestamp INT NOT NULL DEFAULT '0',
-      );");
-
-      /**
-       * The ad_hosts table is used to configure users that can display ads
-       * remotely. 
-       */
-      db_query("CREATE TABLE {ad_hosts} (
-        uid INT NOT NULL DEFAULT '0' PRIMARY KEY,
- 
-        hostid varchar(32) DEFAULT '',
-        status INT NOT NULL DEFAULT '0',
-        description TEXT NOT NULL DEFAULT ''
-      );");
-      break;
-
-    case 'mysql':
-    case 'mysqli':
-    default:
-
-      /* The ad table stores administrative information about each ad.  The
-       * actual ad itself can be found in the appropriate ad type table.
-       */
-      db_query("CREATE TABLE {ads} (
-        aid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-        uid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-  
-        adstatus VARCHAR(255) NOT NULL DEFAULT '',
-        adtype VARCHAR(255) NOT NULL DEFAULT '',
-
-        redirect VARCHAR(255) NOT NULL DEFAULT '',
-
-        autoactivate INT UNSIGNED NOT NULL DEFAULT '0',
-        autoactivated INT UNSIGNED NOT NULL DEFAULT '0',
-        autoexpire INT UNSIGNED NOT NULL DEFAULT '0',
-        autoexpired INT UNSIGNED NOT NULL DEFAULT '0',
-
-        activated INT UNSIGNED NOT NULL DEFAULT '0',
-        maxviews INT UNSIGNED NOT NULL DEFAULT '0',
-        maxclicks INT UNSIGNED NOT NULL DEFAULT '0',
-        expired INT UNSIGNED NOT NULL DEFAULT '0',
-
-        PRIMARY KEY  (aid),
-        KEY (uid),
-        KEY (autoactivate),
-        KEY (autoexpire)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-
-      /**
-       * Every ad can have one or more owners.
-       */
-      db_query("CREATE TABLE {ad_owners} (
-        oid INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
-        aid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-        uid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-
-        PRIMARY KEY  (oid),
-        KEY  (aid),
-        KEY  (uid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-
-      /**
-       * Permissions can be granted to each owner of each ad.  The same owner
-       * can own multiple ads, and can have different permissions for each ad.
-       */
-      db_query("CREATE TABLE {ad_permissions} (
-        oid INT(11) UNSIGNED NOT NULL DEFAULT '0',
-        permissions LONGTEXT NULL,
-        PRIMARY KEY  (oid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-
-      /**
-       * This table counts each time a given action occurs on an ad.  Actions
-       * include when the ad is viewed, clicked, enabled and disabled.
-       * Statistics are collected at an hourly granularity.
-       *
-       * The source column is used for tracking statistics for externally 
-       * hosted ads.
-       *
-       * Actions:
-       *  'view', 'click', 'enable', 'disable'
-       */
-      db_query("CREATE TABLE {ad_statistics} (
-        sid INT UNSIGNED NOT NULL AUTO_INCREMENT,
-        aid INT UNSIGNED NOT NULL DEFAULT '0',
-
-        date INT(10) UNSIGNED NOT NULL DEFAULT '0',
-        action VARCHAR(255) NOT NULL DEFAULT '',
-        adgroup VARCHAR(255) NULL DEFAULT '',
-        hostid VARCHAR(32) NULL DEFAULT '',
-        count INT(11) UNSIGNED NOT NULL DEFAULT '0',
-
-        PRIMARY KEY  (sid),
-        KEY (aid),
-        KEY (date),
-        KEY (action),
-        KEY (adgroup),
-        KEY (hostid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-
-      /**
-       * The ad_clicks table tracks when a given advertisement was clicked, 
-       * who clicked it (uid if any and IP address), and what page they were
-       * on when they clicked it.
-       */
-      db_query("CREATE TABLE {ad_clicks} (
-        cid INT UNSIGNED NOT NULL AUTO_INCREMENT,
-        aid INT UNSIGNED NOT NULL DEFAULT '0',
-        uid int(10) UNSIGNED NOT NULL DEFAULT '0',
-
-        status INT(2) NOT NULL DEFAULT '0',
-
-        hostname varchar(128) NOT NULL DEFAULT '',
-        user_agent varchar(255) NOT NULL DEFAULT '',
-        adgroup varchar(255) NOT NULL DEFAULT '',
-        hostid varchar(32) NOT NULL DEFAULT '',
-        url varchar(255) DEFAULT '',
-        timestamp INT(11) UNSIGNED NOT NULL DEFAULT '0',
-
-        PRIMARY KEY  (cid),
-        KEY  (aid),
-        KEY  (status),
-        KEY  (hostname),
-        KEY  (user_agent),
-        KEY  (adgroup),
-        KEY  (hostid),
-        KEY  (url)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-
-      /**
-       * The ad_hosts table is used to configure users that can display ads
-       * remotely. 
-       */
-      db_query("CREATE TABLE {ad_hosts} (
-        uid INT UNSIGNED NOT NULL DEFAULT '0',
-
-        hostid varchar(32) DEFAULT '',
-
-        status INT(2) UNSIGNED NOT NULL DEFAULT '0',
-        description TEXT NULL,
-
-        PRIMARY KEY  (uid),
-        KEY  (status),
-        KEY  (hostid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-  }
+  return $schema;
+}
 
-  drupal_set_message(t('The necessary ad module tables have been created.'));
+/**
+ * Ad module installation.
+ */
+function ad_install() {
+  // Create tables.
+  drupal_install_schema('ad');
 }
 
 /**
@@ -253,13 +348,79 @@
     variable_del("ad_autoactivate_warning_$node->nid");
   }
 
-  // Drop all ad module tables.
-
   // Delete all remaining ad module variables.
   $variables = array('ad_cron_timestamp', 'ad_link_target', 'ad_cache', 'ad_cache_file', 'adserve', 'ad_group_vid', 'ad_groups', 'ad_validate_url', 'ad_display');
   foreach ($variables as $variable) {
     variable_del($variable);
   }
-  // TODO: "ad_block_quantity_$delta"
+  db_query("DELETE FROM {variable} WHERE name LIKE 'ad_block_quantity_%'");
 }
 
+/**
+ * Convert some things from absolete dev. schema to new schema API
+ */
+function ad_update_6001() {
+  $ret = array();
+  // When we touching index columns, we should first remove it from schema
+  db_drop_index($ret, 'ad_clicks', 'status');
+  db_change_field($ret, 'ad_clicks', 'status', 'status',
+      array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => '',
+      ),
+      array('indexes' => array(
+        'status' => array('status'),
+      ),
+    )
+  );
+  db_drop_index($ret, 'ad_hosts', 'status');
+  db_change_field($ret, 'ad_hosts', 'status', 'status',
+      array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => '',
+      ),
+      array('indexes' => array(
+        'status' => array('status'),
+      ),
+    )
+  );
+
+  db_drop_index($ret, 'ad_statistics', 'hostid');
+  db_change_field($ret, 'ad_statistics', 'hostid', 'hostid',
+      array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Host from which acion was made.',
+      ),
+      array('indexes' => array(
+        'hostid' => array('hostid'),
+      ),
+    )
+  );
+
+  db_drop_index($ret, 'ad_hosts', 'hostid');
+  db_change_field($ret, 'ad_hosts', 'hostid', 'hostid',
+      array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Host from which acion was made.',
+      ),
+      array('indexes' => array(
+        'hostid' => array('hostid'),
+      ),
+    )
+  );
+  return $ret;
+}
Index: ad.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/ad.module,v
retrieving revision 1.2.2.29.2.83.2.5
diff -u -r1.2.2.29.2.83.2.5 ad.module
--- ad.module	26 Jan 2009 03:17:57 -0000	1.2.2.29.2.83.2.5
+++ ad.module	27 Jan 2009 13:48:43 -0000
@@ -1,8 +1,16 @@
 <?php
-// $Id: ad.module,v 1.2.2.29.2.83.2.5 2009/01/26 03:17:57 jeremy Exp $
+// $Id: ad.module,v 1.2.2.29.2.83.2.3 2008/11/19 17:15:10 jeremy Exp $
 
-/* TODO Implement the hook_theme registry. Combine all theme registry entries
-   into one hook_theme function in each corresponding module file.*/
+/**
+ * @file
+ * An advertising system for Drupal powered websites.
+ *
+ */
+
+
+/**
+ * Implementation of hook_theme().
+ */
 function ad_theme() {
   return array(
     'ad_display' => array(
@@ -20,38 +28,32 @@
       ),
     ),
     'ad_statistics_display' => array(
-      'file' => 'ad.module',
+      'file' => 'ad.pages.inc',
       'arguments' => array(
         'statistics' => NULL,
       ),
     ),
     'node_ad' => array(
-      'file' => 'ad.module',
+      'file' => 'ad.pages.inc',
       'arguments' => array(
         'node' => NULL,
         'yield_form' => TRUE,
       ),
     ),
-    'ad_owner_permissions' => array(
-      'file' => 'ad.module',
-      'arguments' => array(
-        'form' => NULL,
-      ),
-    ),
     'ad_admin_ads' => array(
-      'file' => 'ad.module',
+      'file' => 'ad.admin.inc',
       'arguments' => array(
         'form' => NULL,
       ),
     ),
     'ad_filters' => array(
-      'file' => 'ad.module',
+      'file' => 'ad.admin.inc',
       'arguments' => array(
         'form' => NULL,
       ),
     ),
     'ad_filter_form' => array(
-      'file' => 'ad.module',
+      'file' => 'ad.admin.inc',
       'arguments' => array(
         'form' => NULL,
       ),
@@ -59,23 +61,6 @@
   );
 };
 
-/* TODO You may want to take advantage of new form-specific alter hooks.
-   The hook_form_alter(&$form, &$form_state, $form_id) is complemented by hook_form_$form-id_alter().
-   Optionally, modules can implement form-specific alteration functions rather
-   than a single hook_form_alter(&$form, &$form_state, $form_id) with many conditional switch statements.
-   This is optional, and is most useful for tidying the code of modules that
-   alter many forms to customize a site's operations.
-   The new function names are built using the following format:
-   [your-module-name]_form_[$form_id]_alter. */
-
-/**
- * @file
- * An advertising system for Drupal powered websites.
- *
- * Copyright (c) 2005-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.  All rights reserved.
- */
-
 /**
  * Use this function to display ads from a specified group.
  *
@@ -93,13 +78,13 @@
   $adserveinc = variable_get('adserveinc', '');
   if (empty($adserve) || empty($adserveinc)) {
     // This is probably the first time ad() has been called.
-    _ad_check_install();
+    _ad_check_installation();
     $adserve = variable_get('adserve', '');
     $adserveinc = variable_get('adserveinc', '');
   }
   if (!file_exists($adserve) || !file_exists($adserveinc)) {
     drupal_set_message(t('Ads cannot be displayed.  The ad module is <a href="@misconfigured">misconfigured</a>, failed to locate the required <em>serve.php</em> ond/or <em>adserve.inc</em> file.', array('@misconfigured' => url('admin/content/ad/configure'))), 'error');
-    _ad_check_install();
+    _ad_check_installation();
     return (t('The ad module is <a href="@misconfigured">misconfigured</a>.', array('@misconfigured' => url('admin/content/ad/configure'))));
   }
 
@@ -160,7 +145,7 @@
         $query['t'] = $group;
         $options['tids'] = $group;
       }
-      $src = url($base_url. '/'. $adserve, array('query' => $query));
+      $src = url($base_url .'/'. $adserve, array('query' => $query));
       if ($options['ad_display'] == 'iframe') {
         // TODO: We need to know the IFrame size before it is displayed.  This
         // limits the flexibility of what can be displayed in these frames.
@@ -168,21 +153,21 @@
         // over-rides.
         $append = 'frameborder="'. variable_get('ad_iframe_frameborder', 0) .'" ';
         $append .= 'scrolling="'. variable_get('ad_iframe_scroll', 'auto') .'" ';
-        $append .= 'name="'.$group.'" ';
+        $append .= 'name="'. $group .'" ';
         if ($height = variable_get('ad_iframe_height', '')) {
-          $append .= 'height="'.$height.'" ';
+          $append .= 'height="'. $height .'" ';
         }
         if ($width = variable_get('ad_iframe_width', '')) {
-          $append .= 'width="'.$width.'" ';
+          $append .= 'width="'. $width .'" ';
         }
-        $output = '<iframe src="'.$src.'" $append></iframe>';
+        $output = '<iframe src="'. $src .'" $append></iframe>';
       }
       else if ($options['ad_display'] == 'jquery') {
         // The theme function uses this to generate a CSS id for jQuery to use.
         $output = $src;
       }
       else {
-        $output = '<script type="text/javascript" src="'.$src.'"></script>';
+        $output = '<script type="text/javascript" src="'. $src .'"></script>';
       }
       break;
   }
@@ -206,7 +191,7 @@
   $group = preg_replace('/[,]/', '+', $group);
 
   if ($method == 'jquery') {
-    drupal_add_js('misc/jquery.js', 'core', 'header', FALSE, $cache);
+    _drupal_add_js('misc/jquery.js', 'core', 'header', FALSE, $cache);
     return "\n<div class=\"advertisement group-$group\" id=\"group-id-$id\">\n <script type=\"text/javascript\">\n //<!--[CDATA[\n  $(document).ready(function(){ jQuery(\"div#group-id-$id\").load(\"$display\"); });\n //]]>\n </script>\n</div>\n";
   }
   else {
@@ -337,87 +322,6 @@
 }
 
 /**
- * Calculate statistics for the given advertisements.
- * TODO: Introduce caching to make this more efficient.
- */
-function ad_statistics($aid) {
-  // Get global statistics.
-  $statistics['global']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view'", $aid));
-  $statistics['global']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click'", $aid));
-
-  // No sense in making further queries if the ad has no global statistics.
-  if (!$statistics['global']['views'] && !$statistics['global']['clicks']) {
-    return $statistics;
-  }
-
-  // Get statistics for this year and last year.
-  $this_year = date('Y000000');
-  $last_year = date('Y') - 1 .'000000';
-  $statistics['last_year']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $aid, $last_year, $this_year));
-  $statistics['last_year']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $aid, $last_year, $this_year));
-  $statistics['this_year']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $aid, $this_year));
-  $statistics['this_year']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $aid, $this_year));
-
-  // No sense in making further queries if the ad has no statistics this year.
-  if (!$statistics['this_year']['views'] && !$statistics['this_year']['clicks']) {
-    return $statistics;
-  }
-
-  // Get statistics for this month and last month.
-  $this_month = date('Ym0000');
-  $last_month = date('m') - 1;
-  if ($last_month == 0) {
-    $last_month = date('Y') - 1 .'120000';
-  }
-  else {
-    $last_month = date('Y') . ($last_month < 10 ? '0' : '') . $last_month .'0000';
-  }
-  $statistics['last_month']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $aid, $last_month, $this_month));
-  $statistics['last_month']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $aid, $last_month, $this_month));
-  $statistics['this_month']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $aid, $this_month));
-  $statistics['this_month']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $aid, $this_month));
-
-  // No sense in making further queries if the ad has no statistics this month.
-  if (!$statistics['this_month']['views'] && !$statistics['this_month']['clicks']) {
-    return $statistics;
-  }
-
-  // Get statistics for this week.
-  $this_week_start = date('Ymd00', time() - 60*60*24*6);
-  $statistics['this_week']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date > %d", $aid, $this_week_start));
-  $statistics['this_week']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date > %d", $aid, $this_week_start));
-
-  // No sense in making further queries if the ad has no statistics this week.
-  if (!$statistics['this_week']['views'] && !$statistics['this_week']['clicks']) {
-    return $statistics;
-  }
-
-  // Get statistics for yesterday and today.
-  $yesterday_start = date('Ymd00', time() - 60*60*24);
-  $yesterday_end = date('Ymd24', time() - 60*60*24);
-  $today_start = date('Ymd00', time());
-  $statistics['yesterday']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $aid, $yesterday_start, $yesterday_end));
-  $statistics['yesterday']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $aid, $yesterday_start, $yesterday_end));
-  $statistics['today']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $aid, $today_start));
-  $statistics['today']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $aid, $today_start));
-
-  // No sense in making further queries if the ad has no statistics today.
-  if (!$statistics['today']['views'] && !$statistics['today']['clicks']) {
-    return $statistics;
-  }
-
-  // Get statistics for this hour and the last hour.
-  $last_hour = date('YmdH', time() - 60*60);
-  $this_hour = date('YmdH', time());
-  $statistics['last_hour']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date = %d", $aid, $last_hour));
-  $statistics['last_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date = %d", $aid, $last_hour));
-  $statistics['this_hour']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date = %d", $aid, $this_hour));
-  $statistics['this_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date = %d", $aid, $this_hour));
-
-  return $statistics;
-}
-
-/**
  * Display the status of the currently viewed ad.
  */
 function theme_ad_status_display($node) {
@@ -458,29 +362,6 @@
   return theme('box', t('Status'), $output);
 }
 
-function theme_ad_statistics_display($statistics) {
-  $headers = array('', t('Views'), t('Clicks'), t('Click-thru'));
-  $rows = array();
-
-  $data = array('this_hour' => t('This hour'), 'last_hour' => t('Last hour'), 'today' => t('Today'), 'yesterday' => t('Yesterday'), 'this_week' => t('Last seven days'), 'last_week' => t('Last week'), 'this_month' => t('This month'), 'last_month' => t('Last month'), 'this_year' => t('This year'), 'last_year' => t('Last year'), 'global' => t('All time'));
-
-  foreach ($data as $key => $value) {
-    if ($statistics[$key]['views'] || $statistics[$key]['clicks'] || $key == 'global') {
-      $rows[] = array(
-        array('data' => $value),
-        array('data' => (int)$statistics[$key]['views']),
-        array('data' => (int)$statistics[$key]['clicks']),
-        array('data' => $statistics[$key]['views'] ? sprintf('%1.2f', ((int)$statistics[$key]['clicks'] / (int)$statistics[$key]['views']) * 100) .'%' : '0.00%'),
-      );
-    }
-  }
-  return theme('box', t('Statistics'), theme('table', $headers, $rows));
-}
-
-/****************
- * Drupal hooks *
- ****************/
-
 /**
  * Implementation of hook_help().
  */
@@ -494,12 +375,12 @@
 }
 
 /**
- * Drupal _cron hook.
+ * Implementation of hook_cron().
  */
 function ad_cron() {
   if (time() - variable_get('ad_cron_timestamp', 0) >= 60) {
     // Locate ads that need to be activated or expired.
-    $result = db_query('SELECT aid, adstatus, adtype, autoactivate, autoactivated, autoexpire, autoexpired FROM {ads} WHERE autoactivate != 0 OR autoexpire != 0');
+    $result = db_query('SELECT aid, adstatus, adtype, autoactivate, autoactivated, autoexpire, autoexpired FROM {ads} WHERE autoactivate <> 0 OR autoexpire <> 0');
     while ($ad = db_fetch_object($result)) {
       switch ($ad->adstatus) {
         case 'approved': {
@@ -560,9 +441,7 @@
 }
 
 /**
- * Drupal _perm hook.  Establishes permissions used by this module.
- *
- * @return  An array of permissions used by this module.
+ * Implementation of hook_perm().
  */
 function ad_perm() {
   return array('administer advertisements',
@@ -572,17 +451,26 @@
 }
 
 /**
+ * Implementation of hook_node_info().
  */
 function ad_node_info() {
-  return array('ad' => array(
+  $items['ad'] = array(
     'name' => t('Advertisement'),
     'module' => 'ad',
     'description' => t('Advertisements can be randomly displayed to visitors of your website.'),
     'help' => t('Advertisements can be randomly displayed to visitors of your website.'),
-  ));
+  );
+
+  $adtypes = ad_get_types('data');
+  // New 'type' notation returns everything we need to define content type
+  foreach ($adtypes as $type => $data) {
+    $items['ad/'. $type] = $data;
+  }
+  return $items;
 }
 
 /**
+ * Implementation of hook_access().
  */
 function ad_access($op, $node, $account) {
   if ($op == 'create') {
@@ -591,19 +479,18 @@
 
   if ($op == 'update' || $op == 'delete') {
     return (user_access('administer advertisements', $account) ||
-           (ad_is_owner($node->nid) && user_access('edit own advertisements', $account)));
+           (module_exists('ad_owners') && ad_is_owner($node->nid) && user_access('edit own advertisements', $account)));
   }
 }
 
 /**
- * Drupal _form hook.
+ * Implementation of hook_form().
  */
-function ad_form(&$node, $form_state) {
+function ad_form($node, &$form_state) {
   $form = array();
-  $edit = $_POST['edit'];
 
   $type = arg(3);
-  if (function_exists("ad_$type". '_type')) {
+  if (function_exists('ad_'. $type .'_type')) {
     $adtype = $type;
   }
   else {
@@ -631,23 +518,24 @@
 
   // determine the current ad type
   if (!isset($adtype)) {
-    $adtypes = module_invoke_all('adapi', 'type', array());
+    $adtypes = ad_get_types();
     switch (sizeof($adtypes)) {
       case 0:
-        drupal_set_message(t('At least one ad type module must be enabled before you can create advertisements.  For example, try !enabling the ad_text or ad_image module.', array('!enabling' => l('enabling', 'admin/build/modules'))), 'error');
+        drupal_set_message(t('At least one ad type module must be enabled before you can create advertisements.  For example, try <a href="!url">enabling</a> the ad_text or ad_image module.', array('!url' => url('admin/build/modules'))), 'error');
         break;
       case 1:
-        $adtype = $adtypes[0];
+        $adtype = current($adtypes);
         break;
       default:
         if (arg(0) == 'node' && arg(1) == 'add' && arg(2) == 'ad') {
-          $adtype = arg(3) ? arg(3) : $edit['adtype'];
+          $adtype = arg(3) ? arg(3) : $form_state['values']['adtype'];
           $form['adtype'] = array(
             '#type' => 'radios',
             '#title' => t('Style of ad'),
-            '#options' => drupal_map_assoc($adtypes),
+            '#options' => $adtypes,
             '#default_value' => $adtype ? $adtype : $adtypes[0],
             '#required' => TRUE,
+            '#weight' => -10,
             '#description' => t('Select the type of ad that you wish to create from the above options.')
           );
         }
@@ -685,7 +573,7 @@
       );
     }
   }
-  else if (ad_adaccess($node->nid, 'manage status')) {
+  else if (ad_adaccess($node, 'manage status')) {
     if (!$node->adstatus || $node->adstatus == 'pending') {
       $adstatus = ad_status_array();
       $node->adstatus = 'pending';
@@ -743,14 +631,14 @@
     );
   }
 
-  if (ad_adaccess($node->nid, 'access statistics')) {
+  if (ad_adaccess($node, 'access statistics')) {
     // display statistics
     $form['statistics'] = array(
       '#type' => 'fieldset',
       '#title' => t('Statistics'),
       '#collapsible' => TRUE,
     );
-
+    module_load_include('inc', 'ad', 'ad.pages');
     $form['statistics']['data'] = array(
       '#type' => 'markup',
       '#prefix' => '<div>',
@@ -765,14 +653,14 @@
     '#title' => t('Scheduling'),
     '#collapsible' => TRUE,
     // Collapse if there isn't any scheduling data set.
-    '#collapsed' => ($node->autoactivate || $edit['autoactivate'] ||
-                     $node->autoexpire || $edit['autoexpire'] ||
-                     $node->maxviews || $edit['maxviews'] ||
-                     $node->maxclicks || $edit['maxclicks'])
+    '#collapsed' => ($node->autoactivate || $form_state['values']['autoactivate'] ||
+                       $node->autoexpire || $form_state['values']['autoexpire'] ||
+                       $node->maxviews || $form_state['values']['maxviews'] ||
+                       $node->maxclicks || $form_state['values']['maxclicks'])
                      ? FALSE : TRUE,
   );
 
-  if (ad_adaccess($node->nid, 'manage status')) {
+  if (ad_adaccess($node, 'manage status')) {
     $form['schedule']['current'] = array(
       '#type' => 'markup',
       '#prefix' => '<div>',
@@ -834,7 +722,7 @@
 }
 
 /**
- * Drupal _form_alter(&$form, &$form_state, $form_id) hook.
+ * Implementation of hook_form_alter().
  */
 function ad_form_alter(&$form, &$form_state, $form_id) {
   if ($form_id == 'taxonomy_form_vocabulary') {
@@ -854,7 +742,10 @@
       $form['tags']['#description'] = t('If enabled, ads are categorized by typing ad group names instead of choosing them from a list.');
       $form['multiple']['#description'] = t('If enabled, allows ads to have more than one ad group (always true for free tagging).');
       $form['required']['#description'] = t('If enabled, every ad <strong>must</strong> be assigned to at least one ad group.');
-      $form['hierarchy'] = array('#type' => 'value', '#value' => 0);
+      $form['hierarchy'] = array(
+        '#type' => 'value',
+        '#value' => 0
+      );
       unset($form['relations']);
     }
     else {
@@ -871,34 +762,12 @@
       unset($form['synonyms']);
     }
   }
-  else if ($form_id == 'ad_'. arg(4) .'_global_settings' || $form_id == 'ad_no_global_settings') {
-    if (!isset($form['adtype'])) {
-      $form['adtype'] = array('#type' => 'value', '#value' => arg(4));
-    }
-    $permissions = module_invoke_all('adapi', 'permissions', NULL);
-    $form['permissions'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Permissions'),
-      '#collapsible' => TRUE,
-      '#description' => t('Select which permissions will be automatically granted to new owners of !type ads.', array('!type' => arg(4))),
-    );
-    $form['permissions']['default_permissions'] = array(
-      '#type' => 'checkboxes',
-      '#title' => t('Default permissions for !type ad owners', array('!type' => arg(4))),
-      '#options' => drupal_map_assoc($permissions),
-      '#default_value' => variable_get('ad_'. arg(4) .'_default_permissions', array('access statistics', 'access click history', 'manage status')),
-    );
-    if (isset($form['save'])) {
-      $form['save']['#weight'] = 10;
-    }
-    $form['#submit'] = array('ad_global_settings_submit' => array()) + (array)$form['#submit'];
-  }
 }
 
 /**
  * Submit handler for global settings of all ad types.
  *
- * @see ad_form_alter(&$form, &$form_state, $form_id)
+ * @see ad_form_alter()
  */
 function ad_global_settings_submit($form, &$form_state) {
   variable_set('ad_'. $form_state['values']['adtype'] .'_default_permissions', $form_state['values']['default_permissions']);
@@ -906,7 +775,7 @@
 }
 
 /**
- * Drupal _nodeapi hook.
+ * Implementation of hook_nodeapi().
  */
 function ad_nodeapi(&$node, $op, $teaser, $page) {
   global $user;
@@ -930,9 +799,7 @@
           $node->adstatus = 'unpublished';
         }
         $activated = $node->adstatus == 'active' ? time() : 0;
-        db_query("INSERT INTO {ads} (aid, uid, adstatus, adtype, redirect, autoactivate, autoexpire, activated, maxviews, maxclicks) VALUES(%d, %d, '%s', '%s', '%s', %d, %d, %d, %d, %d)", $node->nid, $node->uid, $node->adstatus, $node->adtype, url("ad/redirect/$node->nid", array('absolute' => TRUE)), $node->autoactivate ? strtotime($node->autoactivate) : '', $node->autoexpire ? strtotime($node->autoexpire) : '', $activated, (int)$node->maxviews, (int)$node->maxclicks);
-        ad_owners_add($node->nid, $node->uid);
-        ad_host_id_create($node->uid);
+        db_query("INSERT INTO {ads} (aid, uid, adstatus, adtype, redirect, autoactivate, autoexpire, activated, maxviews, maxclicks) VALUES(%d, %d, '%s', '%s', '%s', %d, %d, %d, %d, %d)", $node->nid, $node->uid, $node->adstatus, $node->adtype, url('ad/redirect/'. $node->nid, array('absolute' => TRUE)), $node->autoactivate ? strtotime($node->autoactivate) : '', $node->autoexpire ? strtotime($node->autoexpire) : '', $activated, (int)$node->maxviews, (int)$node->maxclicks);
         ad_statistics_increment($node->nid, 'create');
       }
       break;
@@ -981,10 +848,7 @@
           ad_statistics_increment($node->nid, $node->adstatus);
         }
         // Update ads table with new information.
-        db_query("UPDATE {ads} SET uid = %d, adstatus = '%s', adtype = '%s', redirect = '%s', autoactivate = %d, autoexpire = %d, activated = %d, maxviews = %d, maxclicks = %d, expired = %d WHERE aid = %d", $node->uid, $node->adstatus, $node->adtype, url("ad/redirect/$node->nid", array('absolute' => TRUE)), $node->autoactivate ? strtotime($node->autoactivate) : '', $node->autoexpire ? strtotime($node->autoexpire) : '', $activated, (int)$node->maxviews, (int)$node->maxclicks, $expired, $node->nid);
-        // Be sure ad owner has at least default ad permissions.
-        ad_owners_add($node->nid, $node->uid);
-        ad_host_id_create($node->uid);
+        db_query("UPDATE {ads} SET uid = %d, adstatus = '%s', adtype = '%s', redirect = '%s', autoactivate = %d, autoexpire = %d, activated = %d, maxviews = %d, maxclicks = %d, expired = %d WHERE aid = %d", $node->uid, $node->adstatus, $node->adtype, url('ad/redirect/'. $node->nid, array('absolute' => TRUE)), $node->autoactivate ? strtotime($node->autoactivate) : '', $node->autoexpire ? strtotime($node->autoexpire) : '', $activated, (int)$node->maxviews, (int)$node->maxclicks, $expired, $node->nid);
         ad_statistics_increment($node->nid, 'update');
       }
       break;
@@ -992,15 +856,6 @@
     case 'delete':
       db_query("DELETE FROM {ads} WHERE aid = %d", $node->nid);
       db_query("DELETE FROM {ad_statistics} WHERE aid = %d", $node->nid);
-      // Clean up ad_permissions and any other per-ad tables.
-      $result = db_query('SELECT oid, uid FROM {ad_owners} WHERE aid = %d', $node->nid);
-      while ($id = db_fetch_object($result)) {
-        db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $id->oid);
-        $owner = user_load(array('uid' => $id->uid));
-        // Tell plug-in modules to clean up.
-        module_invoke_all('adowners', 'remove', $id->oid, $owner);
-      }
-      db_query('DELETE FROM {ad_owners} WHERE aid = %d', $node->nid);
       // All that's left of the ad is a single timestamp as to when it was
       // deleted.
       ad_statistics_increment($node->nid, 'delete');
@@ -1036,125 +891,10 @@
   }
 }
 
-function theme_node_ad($node, $yield_form = TRUE) {
-  $output = '';
-  if (ad_adaccess($node->nid, 'access statistics')) {
-    $output = theme('ad_status_display', $node);
-    $output .= theme('ad_statistics_display', ad_statistics($node->nid));
-  }
-  if (ad_adaccess($node->nid, 'access click history')) {
-    $header = array(
-      array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
-      array('data' => t('IP address'), 'field' => 'hostname'),
-      array('data' => t('URL where clicked'), 'field' => 'url'),
-    );
-    if (function_exists('click_filter_status_text') && user_access('view filtered clicks')) {
-      $header[] = array('data' => t('Status'), 'field' => 'status');
-    }
-    $header[] = array('data' => t(' '));
-
-    if ($node->nid) {
-      $sql = "SELECT cid, timestamp, uid, status, hostname, url FROM {ad_clicks} WHERE aid = $node->nid";
-      $sql .= tablesort_sql($header);
-      $result = pager_query($sql, 25);
-
-      while ($ad = db_fetch_object($result)) {
-        if (module_exists('click_filter') && $ad->status != CLICK_VALID) {
-          // Only show filtered clicks to users with permission to view them.
-          if (!user_access('view filtered clicks')) {
-            continue;
-          }
-        }
-        if (strlen($ad->url) > 40) {
-          $url = substr($ad->url, 0, 37) .'...';
-        }
-        else {
-          $url = $ad->url;
-        }
-        $row = array();
-        $click_user = user_load(array('uid' => $ad->uid));
-        $row[] = format_date($ad->timestamp, 'custom', 'M j H:i');
-        $row[] = $ad->hostname;
-        $row[] = l($url, $ad->url);
-        if (function_exists('click_filter_status_text') && user_access('view filtered clicks')) {
-          $row[] = click_filter_status_text($ad->status);
-        }
-        $row[] = '['. l(t('details'), "node/$node->nid/details/$ad->cid") .']';
-        $rows[] = $row;
-      }
-
-      $click_history = theme('table', $header, $rows);
-      $click_history .= theme('pager', NULL, 25, 0);
-      $output .= theme('box', t('Click history'), $click_history);
-    }
-  }
-  if (variable_get('ad_filter', 0)) {
-    $output = check_markup($output, $node->format);
-  }
-  return $output;
-}
-
-function ad_click_details($nid, $cid) {
-  drupal_set_title(t('Click details'));
-  $node = node_load($nid);
-  drupal_set_breadcrumb(array(l(t('Home'), NULL), l($node->title, "node/$node->nid")));
-  if ($click = db_fetch_object(db_query('SELECT * FROM {ad_clicks} WHERE cid = %d', $cid))) {
-    $ad = node_load($click->aid);
-    $rows = array(
-      array(
-        array('data' => t('Time'), 'header' => TRUE),
-        format_date($click->timestamp, 'custom', 'D F j, Y h:i a'),
-      ),
-      array(
-        array('data' => t('IP Address'), 'header' => TRUE),
-        $click->hostname,
-      ),
-      array(
-        array('data' => t('User Agent'), 'header' => TRUE),
-        $click->user_agent,
-      ),
-      array(
-        array('data' => t('URL'), 'header' => TRUE),
-        l($click->url, $click->url),
-      ),
-      array(
-        array('data' => t('Advertisement'), 'header' => TRUE),
-        $ad->ad,
-      )
-    );
-    if (function_exists('click_filter_status_text') && user_access('view filtered clicks')) {
-      switch ($click->status) {
-        case 0:
-        default:
-          $status = t('Not valid: this click has not been counted for unknown reasons.  This is an unexpected error.');
-          break;
-        case 1:
-          $status = t('Valid: this is a valid click.');
-          break;
-        case 2:
-          $status = t('Not valid: this click has not been counted because another click by the same IP address was already counted.');
-          break;
-        case 3:
-          $status = t('Not valid: this click has not been counted because it was generated by an owner of the advertisement.');
-          break;
-        case 4:
-          $status = t('Not valid: this click has not been counted because it was generated by a user in a filtered role.');
-          break;
-        case 5:
-          $status = t('Not valid: this click has not been counted because it was generated by an automated "bot".');
-          break;
-      }
-      $rows[] = array(array('data' => t('Status'), 'header' => TRUE), $status);
-    }
-    $output = theme('table', array(), $rows, $attributes);
-  }
-  return $output;
-}
-
 function ad_adapi($op, $node = NULL) {
   switch ($op) {
     case 'permissions':
-      return array('access statistics', 'access click history', 'manage status', 'manage owners');
+      return array('access statistics', 'access click history', 'manage status');
       break;
   }
 }
@@ -1163,191 +903,142 @@
  * Implementation of hook_menu().
  */
 function ad_menu() {
-  global $user;
   $items = array();
 
-/* TODO
-   Dynamic menu items under a '!$may_cache' block can often be simplified
-   to remove references to arg(n) and use of '%<function-name>' to check
-   conditions. See http://drupal.org/node/103114.
-
-   The title and description arguments should not have strings wrapped in t(),
-   because translation of these happen in a later stage in the menu system.
-
-   Move administrative functions into their own include files.
-*/
-  // menu items
   $items['admin/content/ad'] = array(
     'title' => 'Ads',
     'page callback' => 'ad_admin_list',
     'access arguments' => array('administer advertisements'),
-    'description' => 'Configure and manage your advertising system.');
-
-  // tabs
+    'description' => 'Configure and manage your advertising system.',
+    'file' => 'ad.admin.inc',
+  );
   $items['admin/content/ad/list'] = array(
     'title' => 'List',
     'page callback' => 'ad_admin_list',
     'access arguments' => array('administer advertisements'),
-    'type' => MENU_DEFAULT_LOCAL_TASK);
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'file' => 'ad.admin.inc',
+  );
   $items['admin/content/ad/statistics'] = array(
     'title' => 'Statistics',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('ad_admin_statistics'),
     'access arguments' => array('administer advertisements'),
     'type' => MENU_LOCAL_TASK,
-    'weight' => 1);
+    'weight' => 1,
+    'file' => 'ad.admin.inc',
+  );
   $items['admin/content/ad/configure'] = array(
     'title' => 'Settings',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('ad_admin_configure_settings'),
     'access arguments' => array('administer advertisements'),
     'type' => MENU_LOCAL_TASK,
-    'weight' => 3);
+    'weight' => 3,
+    'file' => 'ad.admin.inc',
+  );
+
+  ad_menu_add_global_settings($items);
+
   $items['admin/content/ad/groups'] = array(
-    'title' => 'Groups',
+    'title' => 'Ad groups',
     'page callback' => 'ad_admin_groups_list',
     'access arguments' => array('administer advertisements'),
     'type' => MENU_LOCAL_TASK,
-    'weight' => 5);
-
-  // groups sub tabs
+    'weight' => 5,
+    'file' => 'ad.admin.inc',
+  );
   $items['admin/content/ad/groups/list'] = array(
     'title' => 'List',
     'page callback' => 'ad_admin_groups_list',
     'access arguments' => array('administer advertisements'),
     'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => 0);
+    'weight' => 0,
+    'file' => 'ad.admin.inc',
+  );
   $items['admin/content/ad/groups/add'] = array(
     'title' => 'Create group',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('ad_admin_group_form'),
     'access arguments' => array('administer advertisements'),
     'type' => MENU_LOCAL_TASK,
-    'weight' => 3
+    'weight' => 3,
+    'file' => 'ad.admin.inc',
   );
   $items["admin/content/ad/groups/%ad_group/edit"] = array(
     'title' => 'Edit group',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array(
-      'ad_admin_group_form',
-      4,
-    ),
+    'page arguments' => array('ad_admin_group_form', 4),
     'access arguments' => array('administer advertisements'),
     'weight' => 1,
+    'file' => 'ad.admin.inc',
   );
   $items["admin/content/ad/groups/%ad_group/delete"] = array(
     'title' => 'Delete group',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array(
-      'ad_confirm_group_delete',
-      4),
+    'page arguments' => array('ad_confirm_group_delete', 4),
     'access arguments' => array('administer advertisements'),
     'weight' => 2,
+    'file' => 'ad.admin.inc',
   );
-
-  // configure sub tabs
   $items['admin/content/ad/configure/global'] = array(
     'title' => 'Global settings',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('ad_admin_configure_settings'),
     'access arguments' => array('administer advertisements'),
     'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => 0
+    'weight' => 0,
+    'file' => 'ad.admin.inc',
+  );
+  $items["node/%node/details/%"] = array(
+    'title' => 'Click details',
+    'page callback' => 'ad_click_details',
+    'page arguments' => array(1, 3),
+    'type' => MENU_CALLBACK,
+    'file' => 'ad.pages.inc',
   );
-
-  $adtypes = module_invoke_all('adapi', 'type', array());
-  foreach ($adtypes as $adtype) {
-    // Ad type global settings.
-    $settings = "ad_{$adtype}_global_settings";
-    if (!function_exists($settings)) {
-      $settings = 'ad_no_global_settings';
-    }
-    $items['admin/content/ad/configure/'. $adtype] = array(
-      'title' => t('!adtype ads', array('!adtype' => drupal_ucfirst($adtype))),
-      'page callback' => 'drupal_get_form',
-      'page arguments' => array($settings),
-      'access arguments' => array('administer advertisements'),
-      'type' => MENU_LOCAL_TASK,
-      'weight' => 2,
-    );
-    // Node add items.
-    $items['node/add/ad/'. $adtype] = array(
-      'title' => t('!type advertisement', array('!type' => $adtype)),
-      'access arguments' => array('create advertisements'),
-      'file' => _ad_relative_path(realpath(dirname(__FILE__)), $_SERVER['DOCUMENT_ROOT'].base_path()). '/modules/node/node.pages.inc',
-    );
-  }
-
   $items["ad/redirect/%/%/%"] = array(
     'access arguments' => array('show advertisements'),
     'type' => MENU_CALLBACK,
     'page callback' => 'ad_redirect',
-    'page arguments' => (array(2,3,4)),
+    'page arguments' => (array(2, 3, 4)),
   );
   $items["ad/redirect/%/%"] = array(
     'access arguments' => array('show advertisements'),
     'type' => MENU_CALLBACK,
     'page callback' => 'ad_redirect',
-    'page arguments' => (array(2,3)),
+    'page arguments' => (array(2, 3)),
   );
-/***
-  // callbacks
-  if (arg(0) == 'node' && is_numeric(arg(1)) && ad_adaccess(arg(1), 'manage owners')) {
-    $node = node_load(arg(1));
-    if ($node->adtype) {
-      $items["node/$node->nid/adowners"] = array(
-        'access arguments' => ad_adaccess($node->nid, 'manage owners'),
-        'title' => 'Ad owners',
-        'page callback' => 'ad_owners_overview',
-        'page arguments' => array($node),
-        'type' => MENU_LOCAL_TASK,
-        'weight' => 5);
-      $items["node/$node->nid/adowners/list"] = array(
-        'access arguments' => ad_adaccess($node->nid, 'manage owners'),
-        'title' => 'List',
-        'type' => MENU_DEFAULT_LOCAL_TASK,
-        'weight' => 0);
-      if (is_numeric(arg(3))) {
-        $uid = arg(3);
-        $ad_user = user_load(array('uid' => $uid));
-        $items["node/$node->nid/adowners/$uid/permissions"] = array(
-          'title' => '!owner\'s permissions', array('!owner' => $ad_user->name),
-          'access arguments' => ad_adaccess($node->nid, 'manage owners'),
-          'page callback' => 'drupal_get_form',
-          'page arguments' => array('ad_owner_permissions', $node->nid, $uid),
-          'type' => MENU_LOCAL_TASK,
-          'weight' => 2);
-        $items["node/$node->nid/adowners/$uid/remove"] = array(
-          'page callback' => 'drupal_get_form',
-          'page arguments' => array('ad_owner_remove', $node->nid, $uid),
-          'type' => MENU_CALLBACK,
-          'weight' => 6);
-      }
-      else {
-        $items["node/$node->nid/adowners/add"] = array(
-          'access arguments' => ad_adaccess($node->nid, 'manage owners'),
-          'title' => 'Add',
-          'page callback' => 'drupal_get_form',
-          'page arguments' => array('ad_owners_add_form', $node),
-          'type' => MENU_LOCAL_TASK,
-          'weight' => 4);
-      }
+
+  return $items;
+}
+
+/**
+ * Load settings for all ad modules. Those modules, who don't
+ * have their settings form, will get a standart one.
+ */
+function ad_menu_add_global_settings(&$menu_items) {
+  $adtypes = ad_get_types();
+  foreach ($adtypes as $type => $name) {
+    // Ad type global settings.
+    $settings = 'ad_'. $type .'_global_settings';
+    if (!function_exists($settings)) {
+      $settings = 'ad_no_global_settings';
     }
-  }
-  if (is_numeric($nid = arg(1)) && is_numeric($cid = arg(3))) {
-    $items["node/$nid/details/$cid"] = array(
-      'title' => 'Click details',
-      'page callback' => 'ad_click_details',
-      'page arguments' => array($nid, $cid),
-      'type' => MENU_CALLBACK
+    $menu_items['admin/content/ad/configure/'. $type] = array(
+      'title' => $name,
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array($settings),
+      'access arguments' => array('administer advertisements'),
+      'type' => MENU_LOCAL_TASK,
+      'weight' => 2,
+      'file' => 'ad.admin.inc',
     );
   }
-***/
-  return $items;
 }
 
 /**
- * Drupal menu wildcard loader
+ * Drupal menu wildcard Ad group loader
  */
 function ad_group_load($tid) {
   if (!is_numeric($tid)) {
@@ -1361,7 +1052,7 @@
 }
 
 /**
- * Drupal _block hook.
+ * Implementation of hook_block().
  */
 function ad_block($op = 'list', $delta = 0, $edit = array()) {
   switch ($op) {
@@ -1373,1150 +1064,135 @@
       }
       return $blocks;
     case 'configure':
-      $form["ad_block_quantity_$delta"] = array(
+      $form['ad_block_quantity_'. $delta] = array(
         '#type' => 'select',
         '#title' => t('Number of ads'),
-        '#default_value' => variable_get("ad_block_quantity_$delta", 1),
-        '#options' => drupal_map_assoc(array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25)),
+        '#default_value' => variable_get('ad_block_quantity_'. $delta, 1),
+        '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25)),
         '#description' => t('Select the maximum number of unique ads that should be displayed together in this block.  If you specify a number larger than the maximum number of ads in this ad group, all ads will be displayed once.'),
       );
       return $form;
     case 'save':
-      variable_set("ad_block_quantity_$delta", $edit["ad_block_quantity_$delta"]);
+      variable_set('ad_block_quantity_'. $delta, $edit['ad_block_quantity_'. $delta]);
       break;
     case 'view':
       $groups = ad_groups_list();
-      $block['content'] = ad($delta, variable_get("ad_block_quantity_$delta", 1));
+      $block['content'] = ad($delta, variable_get('ad_block_quantity_'. $delta, 1));
       return $block;
   }
 }
 
-/****/
-
 /**
- * Present a list of ad types to choose from.
+ * Determine whether the user has a given privilege.
+ *
+ * @param $ad
+ *   Node object or aid of advertisement.
+ * @param $permission
+ *   Special Ad owners permission which should be checked (such as 'manage owners')
+ * @param $account
+ *   User object, which are accessing the ad or current user by default.
  */
-function ad_add() {
+function ad_adaccess($ad, $permission, $account = NULL) {
   global $user;
+  static $permissions = array();
 
-  $edit = isset($_POST['edit']) ? $_POST['edit'] : '';
-  $adtypes = module_invoke_all('adapi', 'type', array());
-  if (arg(3) && is_array($adtypes) && in_array(arg(3), array_keys($adtypes))) {
-    $adtype = arg(3);
-
-    $node = array(
-      'uid' => $user->uid,
-      'name' => $user->name,
-      'type' => 'ad',
-      'adtype' => $adtype,
-    );
-
-    foreach (array('title', 'teaser', 'body') as $field) {
-      if ($_GET['edit'][$field]) {
-        $node[$field] = $_GET['edit'][$field];
-      }
-    }
-    drupal_set_title(t('Submit %name', array('%name' => $adtype)));
-    $output = drupal_get_form('ad_'.$adtype.'_node_form', $node);
-  }
-  else {
-    $output = t('Choose from the following available advertisement types:');
-    $output .= '<dl>';
-    if (sizeof($adtypes) == 1) {
-      drupal_goto('node/add/ad/'. $adtypes[0]);
-    }
-    else if (sizeof($adtypes)) {
-
-      foreach ($adtypes as $type) {
-        $output .= '<dt>'. l(t('!type advertisement', array('!type' => $type)), "node/add/ad/$type") .'</dt>';
-        // Bugfix #166097: don't array_pop the module_invoke_all directly.
-        // See: http://drupal.org/node/166097
-        $help = module_invoke_all('help', 'node/add/ad#'. $type);
-        $output .= '<dd>'. array_pop($help) .'</dd>';
-      }
-    }
-    else {
-      $output .= '<dt>'. t('There are no advertisement types available.') .'</dt>';
-    }
-    $output .= '</dl>';
+  if (!isset($account)) {
+    $account = $user;
   }
 
-  return $output;
-}
-
-/**
- * TODO: Make this themeable.
- * TODO: Group permissions by module.
- * TODO: Allow modules to define default value for permission.
- */
-function ad_owners_overview($node) {
-  // Be sure the node owner is listed as an ad owner
-  if (!db_result(db_query('SELECT oid FROM {ad_owners} WHERE uid = %d AND aid = %d', $node->uid, $node->nid))) {
-    ad_owners_add($node->nid, $node->uid);
+  // User #1 has all privileges:
+  if ($account->uid == 1) {
+    return TRUE;
   }
 
-  $header = array(
-    array('data' => t('Username'), 'field' => 'uid'),
-    array('data' => t('Options')),
-  );
-
-  drupal_set_title('owners');
-
-  $sql = "SELECT uid FROM {ad_owners} WHERE aid = $node->nid";
-  $sql .= tablesort_sql($header);
-  $result = pager_query($sql, 25);
-
-  $rows = array();
-  while ($ad = db_fetch_object($result)) {
-    $row = array();
-    $user = user_load(array('uid' => $ad->uid));
-    $row[] = $user->name;
-    $options = array();
-    // first option is 'permissions', plug-ins come afterwards
-    $options[] = l(t('permissions'), "node/$node->nid/adowners/$user->uid/permissions");
-    $options = array_merge($options, module_invoke_all('adowners', 'overview', $node->nid, $user->uid));
-    // node owner has to remain an ad owner
-    if ($ad->uid != $node->uid) {
-      $options[] = l(t('remove'), "node/$node->nid/adowners/$user->uid/remove");
-    }
-    $options = implode(' | ', $options);
-    $row[] = $options;
-    $rows[] = $row;
+  // If you have administer permissions, you have all permissions.
+  if (user_access('administer advertisements', $account)) {
+    return TRUE;
   }
 
-  $output = theme('table', $header, $rows);
-  $output .= theme('pager', NULL, 25, 0);
+  // Handle ad owners access
+  if (module_exists('ad_owners')) {
+    return ad_owners_adaccess($ad, $permission, $account);
+  }
 
-  return $output;
+  return FALSE;
 }
 
 /**
- * A simple form for adding new users as owners of ads.
+ * Returns ad types data.
+ *
+ * @param $op
+ *  If set to 'name', will only return array of type names ($type => $name).
+ *  If set to 'data', will return all of the type's data
+ * @param $type
+ *  If not specified, will return array of all available types, else will
+ *  return specific type's name or data.
  */
-function ad_owners_add_form($form_state, $node) {
-  $form = array();
-  drupal_set_title('Add owner');
-
-  $form['aid'] = array(
-    '#type' => 'value',
-    '#value' => $node->nid,
-  );
-  $form['username'] = array(
-    '#autocomplete_path' => 'user/autocomplete',
-    '#description' => t('Enter the username of the user who should have ownership permissions on this advertisement.'),
-    '#required' => TRUE,
-    '#type' => 'textfield',
-    '#title' => t('Username'),
-  );
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Add owner'),
-  );
-
-  return $form;
-}
-
-function ad_owners_add_form_validate($form, &$form_state) {
-  $owner = user_load(array('name' => $form_state['values']['username']));
-  if (!is_object($owner)) {
-    form_set_error('username', t('The specified username %username does not exist.', array('%username' => $form_state['values']['username'])));
-  }
-  else if (db_result(db_query('SELECT oid FROM {ad_owners} WHERE uid = %d AND aid = %d', $owner->uid, $form_state['values']['aid']))) {
-    form_set_error('username', t('The specified user %username is already an owner of this ad.', array('%username' => $form_state['values']['username'])));
-  }
-  else if (!user_access('edit own advertisements', $owner) &&
-           !user_access('administer advertisements', $owner)) {
-    form_set_error('username', t('The specified user %username does not have <em>edit own advertisements</em> nor <em>administer advertisements</em> permissions.  The user must be !assigned to a !role with these privileges before you can add them as an ad owner.', array('%username' => $form_state['values']['username'], '!assigned' => l(t('assigned'), "user/$owner->uid/edit"), '!role' => l(t('role'), 'admin/user/permissions'))));
-  }
-  module_invoke_all('adowners', 'validate', $owner, $form_state['values']['aid']);
-}
-
-function ad_owners_add_form_submit($form, &$form_state) {
-  $owner = user_load(array('name' => $form_state['values']['username']));
-  if (!(ad_owners_add($form_state['values']['aid'], $owner->uid))) {
-    form_set_error('username', t('The user is already an owner of the ad.'));
-  }
-  else {
-    drupal_set_message(t('The user %username has been added as an owner of this advertisement.', array('%username' => $form_state['values']['username'])));
-    drupal_goto('node/'. $form_state['values']['aid'] ."/adowners/$owner->uid/permissions");
-  }
-}
-
-function ad_is_owner($aid, $account = NULL) {
-  global $user;
-  if (is_null($account)) {
-    $account = $user;
-  }
-  if (db_result(db_query('SELECT oid FROM {ad_owners} WHERE uid = %d AND aid = %d', $user->uid, $aid))) {
-    return 1;
-  }
-  else {
-    return 0;
+function ad_get_types($op = 'name', $type = NULL) {
+  $adtypes = module_invoke_all('adapi', 'type', array());
+  switch ($op) {
+    case 'name':
+      if (isset($type)) {
+        return $adtypes[$type]['name'];
+      }
+      else {
+        foreach ($adtypes as $type => $data) {
+          $adtypes[$type] = $data['name'];
+        }
+        return $adtypes;
+      }
+    case 'data':
+      if (isset($type)) {
+        return $adtypes[$type];
+      }
+      else {
+        return $adtypes;
+      }
   }
 }
 
 /**
- * Add an owner to an ad.
+ * Return an array of all groups, or a specific group.
+ *
+ * @param $object
+ *  If FALSE, will return only name of group(s).  If TRUE, will return full
+ *  group object including ->name, ->description, and ->tid.
+ * @param $tid
+ *  If set to an integer >0, will only return group info about that specific
+ *  group.
  */
-function ad_owners_add($aid, $uid, $permissions = array()) {
-  $node = node_load($aid);
-  if ($GLOBALS['db_type'] == 'pgsql') {
-    db_query('START TRANSACTION;');
-  }
-  else {
-    // MySQL, MySQLi
-    db_query('LOCK TABLES {ad_owners} WRITE');
-  }
-
-  if (!db_result(db_query('SELECT oid FROM {ad_owners} WHERE uid = %d AND aid = %d', $uid, $aid))) {
-    db_query('INSERT INTO {ad_owners} (aid, uid) VALUES(%d, %d)', $aid, $uid);
-    $rc = db_affected_rows() ? 1 : 0;
+function ad_groups_list($object = FALSE, $tid = NULL) {
+  static $groups = array();
+  static $names = array();
 
-    if (!$permissions) {
-      $permissions = variable_get('ad_'. $node->adtype .'_default_permissions', array('access statistics', 'access click history', 'manage status'));
+  // Return the full group object(s).
+  if ($object) {
+    if (empty($groups)) {
+      $tids = taxonomy_get_tree(_ad_get_vid());
+      if (is_array($tids)) {
+        foreach ($tids as $group) {
+          $groups[$group->tid]->name = $group->name;
+          $groups[$group->tid]->description = $group->description;
+          $groups[$group->tid]->tid = $group->tid;
+          $groups[$group->tid]->weight = $group->weight;
+        }
+      }
+      // Hard coded "default" group with tid of 0.
+      $groups[0]->name = t('default');
+      $groups[0]->description = t('The default ad group is comprised of all ads not assigned to any other ad group.');
+      $groups[0]->tid = 0;
+      $groups[0]->weight = 0;
     }
-
-    $oid = db_result(db_query("SELECT oid FROM {ad_owners} WHERE aid = %d and uid = %d", $aid, $uid));
-    if ($GLOBALS['db_type'] == 'pgsql') {
-      db_query('START TRANSACTION;');
+    // Return a specific group object.
+    if ((int)$tid) {
+      return $groups[$tid];
     }
+    // Return an array of all group objects.
     else {
-      // MySQL, MySQLi
-      db_query('LOCK TABLES {ad_permissions} WRITE');
+      return $groups;
     }
-    db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $oid);
-    db_query("INSERT INTO {ad_permissions} VALUES(%d, '%s')", $oid, implode('|,|', $permissions));
-    module_invoke_all('adowners', 'add', $node, array('oid' => $oid, 'uid' => $uid, 'aid' => $aid));
-  }
-  if ($GLOBALS['db_type'] == 'pgsql') {
-    db_query('COMMIT;');
   }
-  else {
-    // MySQL, MySQLi
-    db_query('UNLOCK TABLES');
-  }
-  return $rc;
-}
-
-/**
- * Display a form with all available permissions and their status for the
- * selected ad and ad owner.
- */
-function ad_owner_permissions($aid, $uid) {
-  drupal_set_title('Permissions');
-
-  $oid = db_result(db_query("SELECT oid FROM {ad_owners} WHERE aid = %d and uid = %d", $aid, $uid));
-  $granted = explode('|,|', db_result(db_query("SELECT permissions FROM {ad_permissions} WHERE oid = %d", $oid)));
-
-  $form['header'] = array(
-    '#type' => 'value',
-    '#value' => array(t('permissions'), t('granted'))
-  );
-
-  $rows = array();
-
-  $node = node_load($aid);
-  $permissions = module_invoke_all('adapi', 'permissions', $node);
-  foreach ($permissions as $permission) {
-    $form['permission'][$permission] = array(
-      '#value' => t($permission),
-    );
-    $form['grant'][str_replace(' ', '_', $permission)] = array(
-      '#type' => 'checkbox',
-      '#default_value' => in_array($permission, $granted) ? 1 : 0,
-    );
-  }
-
-  $form['oid'] = array(
-    '#type' => 'hidden',
-    '#value' => $oid,
-  );
-
-  $form['aid'] = array(
-    '#type' => 'hidden',
-    '#value' => $aid,
-  );
-
-  $form['uid'] = array(
-    '#type' => 'hidden',
-    '#value' => $uid,
-  );
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
-
-  return $form;
-}
-
-/**
- * Display ad owner permissions in a simple table.
- */
-function theme_ad_owner_permissions($form) {
-  $output = drupal_render($form['options']);
-  foreach (element_children($form['permission']) as $key) {
-    $row = array();
-    $row[] = drupal_render($form['permission'][$key]);
-    $row[] = drupal_render($form['grant'][str_replace(' ', '_', $key)]);
-    $rows[] = $row;
-  }
-
-  $output = theme('table', $form['header']['#value'], $rows);
-  $output .= drupal_render($form);
-  return $output;
-}
-
-/**
- * Store the ad owner's updated permissions in the ad_permissions table.
- */
-function ad_owner_permissions_submit($form, &$form_state) {
-  $permissions = module_invoke_all('adapi', 'permissions', array());
-  $perms = array();
-  foreach ($permissions as $permission) {
-    if ($form_state['values'][str_replace(' ', '_', $permission)]) {
-      $perms[] = $permission;
-    }
-  }
-  if ($GLOBALS['db_type'] == 'pgsql') {
-    db_query('START TRANSACTION;');
-  }
-  else {
-    // MySQL, MySQLi
-    db_query('LOCK TABLES {ad_permissions} WRITE');
-  }
-  db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $form_state['values']['oid']);
-  db_query("INSERT INTO {ad_permissions} VALUES(%d, '%s')", $form_state['values']['oid'], implode('|,|', $perms));
-  if ($GLOBALS['db_type'] == 'pgsql') {
-    db_query('COMMIT;');
-  }
-  else {
-    // MySQL, MySQLi
-    db_query('UNLOCK TABLES');
-  }
-  drupal_set_message(t('The permissions have been saved.'));
-  return 'node/' . $form_state['values'][aid] . '/adowners';
-}
-
-/**
- * Determine whether the user has a given privilege.
- */
-function ad_adaccess($aid, $string, $account = NULL) {
-  global $user;
-  static $permissions = array();
-
-  if (is_null($account)) {
-    $account = $user;
-  }
-
-  // User #1 has all privileges:
-  if ($account->uid == 1) {
-    return TRUE;
-  }
-
-  // If you have administer permissions, you have all permissions.
-  if (user_access('administer advertisements', $account)) {
-    return TRUE;
-  }
-
-  if (!isset($permissions[$aid][$account->uid])) {
-    $oid = db_result(db_query("SELECT oid FROM {ad_owners} WHERE aid = %d and uid = %d", $aid, $account->uid));
-    $permissions[$aid][$account->uid] = explode('|,|', db_result(db_query("SELECT permissions FROM {ad_permissions} WHERE oid = %d", $oid)));
-  }
-
-  return (in_array($string, $permissions[$aid][$account->uid]));
-}
-
-/**
- * Create a unique host id for each ad owner, used when displaying ads remotely.
- */
-function ad_host_id_create($uid) {
-  $hostid = db_result(db_query('SELECT hostid FROM {ad_hosts} WHERE uid = %d', $uid));
-  if (!$hostid) {
-    $hostid = md5($uid . time());
-    db_query("INSERT INTO {ad_hosts} (uid, hostid) VALUES (%d, '%s')", $uid, md5($uid . time()));
-  }
-
-  return $hostid;
-}
-
-/**
- * Build default ad administration page.
- */
-function ad_admin_list() {
-  _ad_check_install();
-
-  $output = drupal_get_form('ad_filter_form');
-
-  if ($_POST['operation'] == 'delete' && $_POST['ads']) {
-    return drupal_get_form('ad_multiple_delete_confirm');
-  }
-  $output .= drupal_get_form('ad_admin_ads');
-
-  return $output;
-}
-
-/**
- * Provide a filterable list of advertisements.
- */
-function ad_admin_ads() {
-  $filter = ad_build_filter_query();
-  $result = pager_query('SELECT a.*, n.* FROM {ads} a INNER JOIN {node} n ON a.aid = n.nid '. $filter['join'] .' '. $filter['where'] .' ORDER BY n.changed DESC', 50, 0, NULL, $filter['args']);
-
-  $form['options'] = array('#type' => 'fieldset',
-    '#title' => t('Update options'),
-    '#prefix' => '<div class="container-inline">',
-    '#suffix' => '</div>',
-  );
-  $options = array();
-  foreach (module_invoke_all('ad_operations') as $operation => $array) {
-    $options[$operation] = $array['label'];
-  }
-  $form['options']['operation'] = array('#type' => 'select', '#options' => $options,  '#default_value' => 'approve');
-  $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
-
-  $destination = drupal_get_destination();
-  while ($ad = db_fetch_object($result)) {
-    $ads[$ad->aid] = '';
-    $form['title'][$ad->aid] = array('#value' => l($ad->title, 'node/'. $ad->aid));
-    $form['group'][$ad->aid] = array('#value' => _ad_get_group($ad->aid));
-    $form['adtype'][$ad->aid] = array('#value' => t($ad->adtype));
-    $form['adstatus'][$ad->aid] = array('#value' => t($ad->adstatus));
-    $form['operations'][$ad->aid] = array('#value' => l(t('edit'), 'node/'. $ad->aid .'/edit', array('query' => $destination)));
-  }
-  $form['ads'] = array('#type' => 'checkboxes', '#options' => $ads);
-  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
-  return $form;
-}
-
-/**
- * Implementation of hook_ad_operations().
- */
-function ad_ad_operations() {
-  $operations = array(
-    'approved' => array(
-      'label' => t('Mark as approved'),
-      'callback' => 'ad_operations_callback',
-      'callback arguments' => array('approved'),
-    ),
-    'active' => array(
-      'label' => t('Mark as active'),
-      'callback' => 'ad_operations_callback',
-      'callback arguments' => array('active'),
-    ),
-    'expired' => array(
-      'label' => t('Mark as expired'),
-      'callback' => 'ad_operations_callback',
-      'callback arguments' => array('expired'),
-    ),
-    'pending' => array(
-      'label' => t('Mark as pending'),
-      'callback' => 'ad_operations_callback',
-      'callback arguments' => array('pending'),
-    ),
-    'offline' => array(
-      'label' => t('Mark as offline'),
-      'callback' => 'ad_operations_callback',
-      'callback arguments' => array('offline'),
-    ),
-    'unpublished' => array(
-      'label' => t('Mark as unpublished'),
-      'callback' => 'ad_operations_callback',
-      'callback arguments' => array('unpublished'),
-    ),
-    'denied' => array(
-      'label' => t('Mark as denied'),
-      'callback' => 'ad_operations_callback',
-      'callback arguments' => array('denied'),
-    ),
-    'delete' => array(
-      'label' => t('Delete'),
-    ),
-  );
-  return $operations;
-}
-
-/**
- * Callback function for admin mass approving ads.
- * TODO: Update activated and expired when appropriate.
- * TODO: Publish/unpublish nodes when appropriate.
- */
-function ad_operations_callback(&$form_state, $ads, $action) {
-  $placeholders = implode(',', array_fill(0, count($ads), '%d'));
-  db_query("UPDATE {ads} SET adstatus = '". $action ."' WHERE aid IN(". $placeholders .')', $ads);
-  foreach ($ads as $aid) {
-    $node = node_load($aid);
-    ad_owners_add($aid, $node->uid);
-    ad_host_id_create($node->uid);
-    ad_statistics_increment($aid, 'update');
-    ad_statistics_increment($aid, $action);
-    // Allow ad type module to act on nodeapi events.  The adapi hook provides
-    // access to additional variables not available in the nodeapi hook.
-    if ($node->adtype) {
-      // Don't use module_invoke, as in pre-PHP5 the changes to $node won't be
-      // passed back.
-      $function = "ad_$node->adtype" .'_adapi';
-      if (function_exists($function)) {
-        $function($op, $node);
-      }
-    }
-    // Allow ad cache module to act on nodeapi events.
-    $cache = variable_get('ad_cache', 'none');
-    if ($cache != 'none') {
-      $function = "ad_cache_$cache" .'_adcacheapi';
-      if (function_exists($function)) {
-drupal_set_message("function($function) action($action)");
-        $function($action, $node);
-      }
-    }
-  }
-}
-
-/**
- * Display a form to confirm whether to really delete the selected ads.
- */
-function ad_multiple_delete_confirm() {
-  $edit = $_POST;
-
-  $form['ads'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
-  // array_filter returns only elements with TRUE values
-  foreach (array_filter($edit['ads']) as $aid => $value) {
-    $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $aid));
-    $form['ads'][$aid] = array('#type' => 'hidden', '#value' => $aid, '#prefix' => '<li>', '#suffix' => check_plain($title) ."</li>\n");
-  }
-  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
-
-  return confirm_form($form,
-                      t('Are you sure you want to delete these ads?'),
-                      'admin/content/ad', t('This action cannot be undone.'),
-                      t('Delete all'), t('Cancel'));
-}
-
-/**
- * Perform the actual ad deletions.
- */
-function ad_multiple_delete_confirm_submit($form, &$form_state) {
-  if ($form_state['values']['confirm']) {
-    foreach ($form_state['values']['ads'] as $aid => $value) {
-      node_delete($aid);
-    }
-    drupal_set_message(t('The ads have been deleted.'));
-  }
-  $form_state['redirect'] = 'admin/content/ad';
-}
-
-/**
- * Theme ad administration overview.
- */
-function theme_ad_admin_ads($form) {
-  // Overview table:
-  $header = array(theme('table_select_header_cell'), t('Title'), t('Group'), t('Type'), t('Status'), t('Operations'));
-
-  $output .= drupal_render($form['options']);
-  if (isset($form['title']) && is_array($form['title'])) {
-    foreach (element_children($form['title']) as $key) {
-      $row = array();
-      $row[] = drupal_render($form['ads'][$key]);
-      $row[] = drupal_render($form['title'][$key]);
-      $row[] = drupal_render($form['group'][$key]);
-      $row[] = drupal_render($form['adtype'][$key]);
-      $row[] = drupal_render($form['adstatus'][$key]);
-      $row[] = drupal_render($form['operations'][$key]);
-      $rows[] = $row;
-    }
-
-  }
-  else  {
-    $rows[] = array(array('data' => t('No ads available.'), 'colspan' => '6'));
-  }
-
-  $output .= theme('table', $header, $rows);
-  if ($form['pager']['#value']) {
-    $output .= drupal_render($form['pager']);
-  }
-
-  $output .= drupal_render($form);
-
-  return $output;
-}
-
-/**
- * Must select an ad if performing an operation.
- */
-function ad_admin_ads_validate($form, &$form_state) {
-  $ads = array_filter($form_state['values']['ads']);
-  if (count($ads) == 0) {
-    form_set_error('', t('No ads selected.'));
-  }
-}
-
-/**
- * Submit the ad administration update form.
- */
-function ad_admin_ads_submit($form, &$form_state) {
-  $operations = module_invoke_all('ad_operations');
-  $operation = $operations[$form_state['values']['operation']];
-  // Filter out unchecked nodes
-  $ads = array_filter($form_state['values']['ads']);
-  if ($function = $operation['callback']) {
-    // Add in callback arguments if present.
-    if (isset($operation['callback arguments'])) {
-      $args = array_merge(array($ads), $operation['callback arguments']);
-    }
-    else {
-      $args = array($ads);
-    }
-    call_user_func_array($function, $args);
-
-    cache_clear_all();
-    drupal_set_message(t('The update has been performed.'));
-  }
-}
-
-/**
- * Build query for ad administration filters based on session.
- */
-function ad_build_filter_query() {
-  $filters = ad_filters();
-
-  // Build query
-  $where = $args = array();
-  $join = '';
-  foreach ($_SESSION['ad_overview_filter'] as $index => $filter) {
-    list($key, $value) = $filter;
-    switch ($key) {
-      case 'status':
-        list($value, $key) = explode('-', $value, 2);
-        $op = $key == 1 ? '=' : '!=';
-        $where[] = "a.adstatus $op '%s'";
-        break;
-      case 'group':
-        $table = "tn$index";
-        $where[] = "$table.tid = %d";
-        $join .= "INNER JOIN {term_node} $table ON n.nid = $table.nid ";
-        break;
-      case 'type':
-        $where[] = "a.adtype = '%s'";
-        $adtypes = module_invoke_all('adapi', 'type', array());
-        $value = $adtypes[$value];
-    }
-    $args[] = $value;
-  }
-  $where = count($where) ? 'WHERE '. implode(' AND ', $where) : '';
-
-  return array('where' => $where, 'join' => $join, 'args' => $args);
-}
-
-/**
- * Theme ad administration filter selector.
- */
-function theme_ad_filters($form) {
-  $output .= '<ul class="clear-block">';
-  if (sizeof($form['current'])) {
-    foreach (element_children($form['current']) as $key) {
-      $output .= '<li>'. drupal_render($form['current'][$key]) .'</li>';
-    }
-  }
-
-  $output .= '<li><dl class="multiselect">'. (sizeof($form['current']) ? '<dt><em>'. t('and') .'</em> '. t('where') .'</dt>' : '') .'<dd class="a">';
-  foreach (element_children($form['filter']) as $key) {
-    $output .= drupal_render($form['filter'][$key]);
-  }
-  $output .= '</dd>';
-
-  $output .= '<dt>'. t('is') .'</dt><dd class="b">';
-
-  foreach (element_children($form['status']) as $key) {
-    $output .= drupal_render($form['status'][$key]);
-  }
-  $output .= '</dd>';
-
-  $output .= '</dl>';
-  $output .= '<div class="container-inline" id="ad-admin-buttons">'. drupal_render($form['buttons']) .'</div>';
-  $output .= '</li></ul>';
-
-  return $output;
-}
-
-/**
- * List ad administration filters that can be applied.
- */
-function ad_filters() {
-  $session = &$_SESSION['ad_overview_filter'];
-  $session = is_array($session) ? $session : array();
-  // Regular filters
-  $options = array();
-  $options = array('pending-1' => t('pending'), 'approved-1' => t('approved'), 'active-1' => t('active'), 'offline-1' => t('offline'), 'unpublished-1' => t('unpublished'), 'expired-1' => t('expired'), 'denied-1' => t('denied'), 'pending-0' => t('not pending'), 'approved-0' => t('not approved'), 'active-0' => t('not active'), 'offline-0' => t('not offline'), 'unpublished-0' => t('not unpublished'), 'expired-0' => t('not expired'), 'denied-0' => t('not denied'));
-
-  $filters['status'] = array('title' => t('status'),
-    'options' => $options);
-  $filters['type'] = array('title' => t('type'), 'options' => module_invoke_all('adapi', 'type', array()));
-  // The taxonomy filter
-  if ($taxonomy = module_invoke('taxonomy', 'get_tree', _ad_get_vid())) {
-    $options = array();
-    // TODO: Add support for the default group.
-    //$options[0] = t('default');
-    foreach ($taxonomy as $term) {
-      $options[$term->tid] = $term->name;
-    }
-    $filters['group'] = array('title' => t('group'), 'options' => $options);
-  }
-
-  return $filters;
-}
-
-/**
- * Return form for advertisement administration filters.
- */
-function ad_filter_form($form_state) {
-  $session = &$_SESSION['ad_overview_filter'];
-  $session = is_array($session) ? $session : array();
-  $filters = ad_filters();
-
-  $i = 0;
-  $form['filters'] = array('#type' => 'fieldset',
-    '#title' => t('Show only ads where'),
-    '#theme' => 'ad_filters',
-  );
-  foreach ($session as $filter) {
-    list($type, $value) = $filter;
-    if ($type == 'category') {
-      // Load term name from DB rather than search and parse options array.
-      $value = module_invoke('taxonomy', 'get_term', $value);
-      $value = $value->name;
-    }
-    else if ($type == 'status') {
-      $value = $filters['status']['options'][$value];
-    }
-    else {
-      $value = $filters[$type]['options'][$value];
-    }
-    $string = ($i++ ? '<em>and</em> where <strong>%a</strong> is <strong>%b</strong>' : '<strong>%a</strong> is <strong>%b</strong>');
-    $form['filters']['current'][] = array('#value' => t($string, array('%a' => $filters[$type]['title'] , '%b' => $value)));
-    if ($type == 'type') {
-      // Remove the type option if it is already being filtered on.
-      unset($filters['type']);
-    }
-    else if ($type == 'group') {
-      unset($filters['group']);
-    }
-    if ($type == 'status') {
-      foreach ($session as $option) {
-        if ($option[0] == 'status') {
-          list($value, $key) = explode('-', $option[1], 2);
-          if ($key) {
-            // One postive key means we can't have any more.
-            // Remove the status option if we're already filtering on a positive
-            // key (ie, 'active', as an ad can't be 'active' and 'pending')
-            unset($filters['status']);
-          }
-          else {
-            // When a key is selected, remove it and its inverse as there's
-            // no logic in selecting the same key multiple times, and selecting
-            // two opposite keys will always return 0 results.
-            $inverse = $key == 1 ? 0 : 1;
-            unset($filters['status']['options'][$option[1]]);
-            unset($filters['status']['options'][$value.'-'.$inverse]);
-          }
-        }
-      }
-    }
-  }
-
-  foreach ($filters as $key => $filter) {
-    $names[$key] = $filter['title'];
-    $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
-  }
-
-  $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
-  $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter')));
-  if (count($session)) {
-    $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
-    $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
-  }
-
-  return $form;
-}
-
-/**
- * Theme ad administration filter form.
- */
-function theme_ad_filter_form($form) {
-  $output .= '<div id="ad-admin-filter">';
-  $output .= drupal_render($form['filters']);
-  $output .= '</div>';
-  $output .= drupal_render($form);
-  return $output;
-}
-
-/**
- * Process result from ad administration filter form.
- */
-function ad_filter_form_submit($form, &$form_state) {
-  $filters = ad_filters();
-/* TODO The 'op' element in the form values is deprecated.
-   Each button can have #validate and #submit functions associated with it.
-   Thus, there should be one button that submits the form and which invokes
-   the normal form_id_validate and form_id_submit handlers. Any additional
-   buttons which need to invoke different validate or submit functionality
-   should have button-specific functions. */
-  switch ($form_state['values']['op']) {
-    case t('Filter'):
-    case t('Refine'):
-      if (isset($form_state['values']['filter'])) {
-        $filter = $form_state['values']['filter'];
-
-        // Flatten the options array to accommodate hierarchical/nested options.
-        $flat_options = form_options_flatten($filters[$filter]['options']);
-
-        if (isset($flat_options[$form_state['values'][$filter]])) {
-          $_SESSION['ad_overview_filter'][] = array($filter, $form_state['values'][$filter]);
-        }
-      }
-      break;
-    case t('Undo'):
-      array_pop($_SESSION['ad_overview_filter']);
-      break;
-    case t('Reset'):
-      $_SESSION['ad_overview_filter'] = array();
-      break;
-  }
-}
-
-/**
- *
- */
-function ad_admin_statistics() {
-  $groups = ad_groups_list(TRUE);
-  foreach ($groups as $tid => $group) {
-    if ($tid) {
-      $ads = db_result(db_query("SELECT count(aid) FROM {ads} a JOIN {term_node} t ON a.aid = t.nid WHERE t.tid = %d AND adstatus = 'active'", $tid));
-      $filter = '= ' . $tid;
-    }
-    else {
-      $ads = db_result(db_query("SELECT count(aid) FROM {ads} a LEFT JOIN {term_node} t ON a.aid = t.nid WHERE t.tid IS NULL AND adstatus = 'active'"));
-      $filter = "IS NULL";
-    }
-    if (!$ads) {
-      continue;
-    }
-
-    $form[$group->name] = array(
-      '#type' => 'fieldset',
-      '#title' => $group->name,
-      '#collapsible' => TRUE,
-    );
-
-    // Get overall global statistics.
-    $statistics['global']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND n.tid %s", $filter));
-    $statistics['global']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND n.tid %s", $filter));
-
-    // Get overall statistics for this year and last year.
-    $this_year = date('Y000000');
-    $last_year = date('Y') - 1 .'000000';
-    $statistics['last_year']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND date <= %d AND n.tid %s", $last_year, $this_year, $filter));
-    $statistics['last_year']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND date <= %d AND n.tid %s", $last_year, $this_year, $filter));
-    $statistics['this_year']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $this_year, $filter));
-    $statistics['this_year']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $this_year, $filter));
-
-    // Get statistics for this month and last month.
-    $this_month = date('Ym0000');
-    $last_month = date('m') - 1;
-    if ($last_month == 0) {
-      $last_month = date('Y') - 1 .'120000';
-    }
-    else {
-      $last_month = date('Y'). ($last_month < 10 ? '0' : '') . $last_month .'0000';
-    }
-    $statistics['last_month']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND date <= %d AND n.tid %s", $last_month, $this_month, $filter));
-    $statistics['last_month']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND date <= %d AND n.tid %s", $last_month, $this_month, $filter));
-    $statistics['this_month']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $this_month, $filter));
-    $statistics['this_month']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $this_month, $filter));
-
-    // Get statistics for this week.
-    $this_week_start = date('Ymd00', time() - 60*60*24*6);
-    $statistics['this_week']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $this_week_start, $filter));
-    $statistics['this_week']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $this_week_start, $filter));
-
-    // Get statistics for yesterday and today.
-    $yesterday_start = date('Ymd00', time() - 60*60*24);
-    $yesterday_end = date('Ymd24', time() - 60*60*24);
-    $today_start = date('Ymd00', time());
-    $statistics['yesterday']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND date <= %d AND n.tid %s", $yesterday_start, $yesterday_end, $filter));
-    $statistics['yesterday']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND date <= %d AND n.tid %s", $yesterday_start, $yesterday_end, $filter));
-    $statistics['today']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $today_start, $filter));
-    $statistics['today']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $today_start, $filter));
-
-    // Get statistics for this hour and the last hour.
-    $last_hour = date('YmdH', time() - 60*60);
-    $this_hour = date('YmdH', time());
-    $statistics['last_hour']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date = %d AND n.tid %s", $last_hour, $filter));
-    $statistics['last_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date = %d AND n.tid %s", $last_hour, $filter));
-    $statistics['this_hour']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date = %d AND n.tid %s", $this_hour, $filter));
-    $statistics['this_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date = %d AND n.tid %s", $this_hour, $filter));
-
-    // TODO: Create this view and remove the && FALSE to enable this code.
-    if (module_exists('views') && FALSE) {
-      $form[$group->name]['statistics'] = array(
-        '#type' => 'markup',
-        '#value' => '<p>'. t('There %count in this group.', array('%count' => format_plural($ads, 'is '. l('1 active ad', "ad/". $group->gid ."/group"), 'are '. l('%count active ads', "ad/$group->tid/group")))) .'</p>' . theme('ad_statistics_display', $statistics),
-      );
-    }
-    else {
-      $form[$group->name]['statistics'] = array(
-        '#type' => 'markup',
-        '#value' => '<p>'. t('There @count in this group.', array('@count' => format_plural($ads, 'is 1 active ad', 'are @count active ads'))) .'</p>' . theme('ad_statistics_display', $statistics),
-      );
-    }
-  }
-
-  if (count($form) == 0) {
-    $form['header'] = array(
-      '#type' => 'markup',
-      '#value' => '<p>'. t('There are no active ads.') .'</p>',
-    );
-  }
-
-  return $form;
-}
-
-/**
- * Display a form for the ad module settings.
- */
-function ad_admin_configure_settings($edit = array()) {
-  _ad_check_install();
-
-  $edit = $_POST['edit'];
-
-  $adserve = variable_get('adserve', '');
-  $adserveinc = variable_get('adserveinc', '');
-  $form['configuration'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Status'),
-  );
-  $form['configuration']['adserve'] = array(
-    '#type' => 'markup',
-    '#value' => t('Using detected adserve scripts: %adserve, %adserveinc', array('%adserve' => ($adserve ? $adserve : t('not found')), '%adserveinc' => ($adserveinc ? $adserveinc : t('not found')))),
-  );
-
-  $form['general'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('General'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE,
-  );
-
-  // TODO: This needs a per-group over-ride, in case some groups are IFrames,
-  // while others are JavaScript, etc.
-  $form['general']['ad_link_target'] = array(
-    '#type' => 'radios',
-    '#title' => t('Click-through target'),
-    '#options' => array(
-      '_self' => t('same browser window and frame'),
-      '_blank' => t('new browser window'),
-      '_parent' => t('parent frame'),
-      '_top' => t('same browser window, removing all frames'),
-    ),
-    '#default_value' => variable_get('ad_link_target', '_self'),
-    '#description' => t('Select an option above to configure what happens when an ad is clicked.  These options set the <em>a target</em>, and are <em>_self</em>, <em>_blank</em>, <em>_parent</em> and <em>_top</em> respectively.'),
-  );
-
-  $form['general']['ad_link_nofollow'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('nofollow'),
-    '#default_value' => variable_get('ad_link_nofollow', 0),
-    '#description' => t('If enabled, %tag will be added to advertisement links generated by this module.', array('%tag' => t('rel="nofollow"'))),
-  );
-
-  // Provide hook for ad_display_TYPE modules to set display TYPE.
-  $display_options = array_merge(array('javascript' => t('JavaScript'), 'jquery' => t('jQuery'), 'iframe' => t('IFrame'), 'raw' => t('Raw')), module_invoke_all('displayapi', 'display_method'), array());
-
-  // Provide hook for ad_display_TYPE modules to define inline description.
-  $description = t('This setting configures the default method for displaying advertisements on your website.  It is possible to override this setting when making direct calls to ad(), as described in the documentation.  Using the JavaScript, jQuery, and IFrame display methods allows you to display random ads and track views even on cached pages.  When using the Raw display method together with Drupal\'s page cache, view statistics will be properly tracked but advertisements will only change when the page cache is updated.');
-  $return = module_invoke_all('displayapi', 'display_description', array());
-  foreach ($return as $describe) {
-    $description .= ' '. $describe;
-  }
-
-  $form['general']['ad_display'] = array(
-    '#type' => 'radios',
-    '#title' => t('Display type'),
-    '#default_value' => variable_get('ad_display', 'javascript'),
-    '#options' => $display_options,
-    '#description' => $description,
-  );
-
-  $form['general']['ad_validate_url'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Validate URLs'),
-    '#default_value' => variable_get('ad_validate_url', 1),
-    '#description' => t('If enabled, any destination URLs entered in ads will be required to be complete URLs (including http:// or https:// at the beginning).  If you wish to include internal urls, you will need to disable this option.'),
-  );
-
-  $form['general']['ad_filter'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Filter ads'),
-    '#default_value' => variable_get('ad_filter', 0),
-    '#description' => t('If enabled, the input format for each advertisement node will be applied to the displayed advertisement.'),
-  );
-
-  $form['iframe'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('IFrame'),
-    '#collapsible' => TRUE,
-    '#collapsed' => variable_get('ad_display', 'javascript') == 'iframe' ? FALSE : TRUE
-  );
-  $form['iframe']['ad_iframe_frameborder'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Frameborder'),
-    '#default_value' => variable_get('ad_iframe_frameborder', 0),
-    '#description' => t('If enabled, IFrames used for displaying ads will have a frameborder.'),
-  );
-  $form['iframe']['ad_iframe_scroll'] = array(
-    '#type' => 'radios',
-    '#title' => t('Scrolling'),
-    '#default_value' => variable_get('ad_iframe_scroll', 'auto'),
-    '#options' => array('auto' => 'auto', 'on' => 'on', 'off' => 'off'),
-    '#description' => t('Define whether or not scroll bars should be enabled for the ad IFrame.'),
-  );
-  $form['iframe']['ad_iframe_width'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Width'),
-    '#default_value' => variable_get('ad_iframe_width', ''),
-    '#maxlength' => 8,
-    '#size' => 5,
-    '#required' => FALSE,
-    '#description' => t('The default width for advertisement IFrames'),
-  );
-  $form['iframe']['ad_iframe_height'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Height'),
-    '#default_value' => variable_get('ad_iframe_height', ''),
-    '#maxlength' => 8,
-    '#size' => 5,
-    '#required' => FALSE,
-    '#description' => t('The default height for advertisement IFrames'),
-  );
-
-  $form['cache'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Cache'),
-    '#collapsible' => TRUE,
-    '#collapsed' => variable_get('ad_cache', 'none') == 'none' ? TRUE : FALSE,
-  );
-
-  // Provide hook for ad_cache_TYPE modules to set cache TYPE.
-  $cache_options = array_merge(array('none' => t('None')), module_invoke_all('adcacheapi', 'method', array()));
-
-  // Provide hook for ad_cache_TYPE modules to define inline description.
-  $description = t('A cache can be used to efficiently track how many times advertisements are displayed and clicked.');
-  $return = module_invoke_all('adcacheapi', 'description', array());
-  foreach ($return as $describe) {
-    $description .= ' '. $describe;
-  }
-
-  $form['cache']['ad_cache'] = array(
-    '#type' => 'radios',
-    '#title' => t('Type'),
-    '#default_value' => variable_get('ad_cache', 'none'),
-    '#options' => $cache_options,
-    '#description' => $description,
-  );
-
-  // Provide hook for ad_cache_TYPE modules to add inline settings.
-  $form['cache'] = array_merge($form['cache'], module_invoke_all('adcacheapi', 'settings', $edit));
-
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
-
-  return $form;
-}
-
-/**
- * Validate form settings, calling attention to any illogical configurations.
- */
-function ad_admin_configure_settings_validate($form, &$form_state) {
-  if ($form_state['values']['ad_link_target'] == '_self' &&
-      $form_state['values']['ad_display'] == 'iframe') {
-    // We don't consider this an error, as this could be exactly what the
-    // administrator is trying to do.  But as for most people it is likely
-    // to be a misconfiguration, display a helpful warning...
-    drupal_set_message('You have configured your advertisements to be displayed in iframes, and you have configured your click-through target as "same browser window and frame".  This is an unusual configuration, as when you click your advertisements only the IFrame will be redirected.  Be sure that this is actually what you are trying to do.');
-  }
-}
-
-/**
- * Save updated values from settings form.
- */
-function ad_admin_configure_settings_submit($form, &$form_state) {
-  variable_set('ad_link_target', $form_state['values']['ad_link_target']);
-  variable_set('ad_link_nofollow', $form_state['values']['ad_link_nofollow']);
-  variable_set('ad_cache', $form_state['values']['ad_cache']);
-  variable_set('ad_display', $form_state['values']['ad_display']);
-  variable_set('ad_validate_url', $form_state['values']['ad_validate_url']);
-  variable_set('ad_filter', $form_state['values']['ad_filter']);
-  variable_set('ad_iframe_frameborder', $form_state['values']['ad_iframe_frameborder']);
-  variable_set('ad_iframe_scroll', $form_state['values']['ad_iframe_scroll']);
-  variable_set('ad_iframe_width', $form_state['values']['ad_iframe_width']);
-  variable_set('ad_iframe_height', $form_state['values']['ad_iframe_height']);
-  if (($cache = variable_get('ad_cache', 'none')) != 'none') {
-    // Allow external cache types to store their settings
-    module_invoke('ad_cache_'. $cache, 'adcacheapi', 'settings_submit', $form_state['values']);
-  }
-/*
- // TODO: Write an external display module and implement this.
-  $display = variable_get('ad_display', 'javascript');
-  if ($display != 'javascript' && $display != 'raw') {
-    // Allow external display types to store their settings
-    module_invoke('ad_cache_'. $cache, 'adcacheapi', 'settings_submit', $form_state['values']);
-  }
-*/
-}
-
-/**
- * Return an array of all groups, or a specific group.
- *
- * @param $tid
- *  If set to an integer >0, will only return group info about that specific
- *   group.
- * @object
- *  If FALSE, will return only name of group(s).  If TRUE, will return full
- *  group object including ->name, ->description, and ->tid.
- */
-function ad_groups_list($object = FALSE, $tid = NULL) {
-  static $groups = array();
-  static $names = array();
-
-  // Return the full group object(s).
-  if ($object) {
-    if (empty($groups)) {
-      $tids = taxonomy_get_tree(_ad_get_vid());
-      if (is_array($tids)) {
-        foreach ($tids as $group) {
-          $groups[$group->tid]->name = $group->name;
-          $groups[$group->tid]->description = $group->description;
-          $groups[$group->tid]->tid = $group->tid;
-          $groups[$group->tid]->weight = $group->weight;
-        }
-      }
-      // Hard coded "default" group with tid of 0.
-      $groups[0]->name = t('default');
-      $groups[0]->description = t('The default ad group is comprised of all ads not assigned to any other ad group.');
-      $groups[0]->tid = 0;
-      $groups[0]->weight = 0;
-    }
-    // Return a specific group object.
-    if ((int)$tid) {
-      return $groups[$tid];
-    }
-    // Return an array of all group objects.
-    else {
-      return $groups;
-    }
-  }
-  // Return only the group name(s).
+  // Return only the group name(s).
   else {
     if (empty($names)) {
       $tids = taxonomy_get_tree(_ad_get_vid());
@@ -2539,97 +1215,6 @@
   }
 }
 
-function ad_admin_groups_list() {
-  _ad_check_install();
-
-  $header = array(
-      array('data' => t('Name'), 'field' => 'name'),
-      array('data' => t('Description'), 'field' => 'description'),
-      array('data' => t('Options')),
-    );
-
-  $groups = taxonomy_get_tree(_ad_get_vid());
-
-  if ($groups != array()) {
-    foreach ($groups as $group) {
-      $row = array();
-      $row[] = $group->name;
-      $row[] = $group->description;
-      $row[] = l(t('edit'), "admin/content/ad/groups/$group->tid/edit");
-      $rows[] = $row;
-    }
-  }
-  else {
-    $rows[] = array(array('data' => t('No groups have been created.'), 'colspan' => 3));
-  }
-
-  $output = theme('table', $header, $rows);
-  $output .= theme('pager', NULL, 15, 0);
-
-  return $output;
-}
-
-function ad_owner_remove($aid, $uid) {
-  $form['aid'] = array(
-    '#type' => 'value',
-    '#value' => $aid,
-  );
-  $form['uid'] = array(
-    '#type' => 'value',
-    '#value' => $uid,
-  );
-  $owner = user_load(array('uid' => $uid));
-  return confirm_form($form,
-    t('Are you sure you want to remove user %name as an owner of this advertisement?', array('%name' => $owner->name)),
-    "node/$aid/adowners",
-    t('This action cannot be undone.'),
-    t('Remove'),
-    t('Cancel')
-  );
-}
-
-/**
- * Don't allow the removal of the primary owner of the advertisement.
- */
-function ad_owner_remove_validate($form, &$form_state) {
-  $node = node_load($form_state['values']['aid']);
-  if ($node->uid == $form_state['values']['uid']) {
-    $owner = user_load(array('uid' => $form_state['values']['uid']));
-    drupal_set_message(t('%name is the primary owner of this advertisement.  You cannot remove the primary owner.', array('%name' => $owner->name)), 'error');
-
-    $form_state['redirect'] = 'node/'. $form_state['values']['aid'] .'/adowners';
-  }
-}
-
-/**
- * Remove the ad owner, and all associated permissions.
- */
-function ad_owner_remove_submit($form, &$form_state) {
-  $oid = db_result(db_query('SELECT oid FROM {ad_owners} WHERE aid = %d AND uid = %d', $form_state['values']['aid'], $form_state['values']['uid']));
-  db_query('DELETE FROM {ad_owners} WHERE oid = %d', $oid);
-  db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $oid);
-  $owner = user_load(array('uid' => $form_state['values']['uid']));
-  module_invoke_all('adowners', 'remove', $oid, $owner);
-  drupal_set_message(t('The ad owner %name has been removed.', array('%name' => $owner->name)));
-
-  $form_state['redirect'] = 'node/'. $form_state['values']['aid'] .'/adowners';
-}
-
-/**
- * Empty page for ad_type modules that don't define a global settings page.
- * This way admins can still set default permissions for this ad type.
- */
-function ad_no_global_settings() {
-  $form = array();
-
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
-
-  return $form;
-}
-
 /**
  * Implement ad notify api _hook.
  */
@@ -2707,9 +1292,7 @@
   }
 }
 
-/****/
-
-function _ad_check_install() {
+function _ad_check_installation() {
   // Verify serve.php exists and is readable.
   $adserve = variable_get('adserve', '');
   $adserveinc = variable_get('adserveinc', '');
@@ -2769,7 +1352,7 @@
     variable_set('ad_group_vid_restore', '');
   }
 
-  $rid = db_result(db_query("SELECT rid FROM {permission} WHERE perm LIKE '%%show advertisements%%' LIMIT 1"));
+  $rid = db_result(db_query_range("SELECT rid FROM {permission} WHERE perm LIKE '%%show advertisements%%'", 1));
   if (!$rid) {
     drupal_set_message(t('Be sure to enable "!show" permissions for all roles that you wish to see advertisements.', array('!show' => l(t('show advertisements'), 'admin/user/permissions'))));
   }
@@ -2798,8 +1381,8 @@
         // weight is an optional, defaults to 0
         $weight = $action['weight'] = 0;
       }
-      $actions[$weight. '.' .$name] = $action;
-      $actions[$weight. '.' .$name]['name'] = $name;
+      $actions[$weight .'.'. $name] = $action;
+      $actions[$weight .'.'. $name]['name'] = $name;
     }
     // order actions by weight (multiple same-weight actions sorted by alpha)
     ksort($actions);
@@ -2839,105 +1422,6 @@
 }
 
 /**
- * Returns a form for adding an ad group.
- */
-function ad_admin_group_form($form_state, $group = NULL) {
-  if ($_POST['op'] == t('Delete') || $_POST['edit']['confirm']) {
-    drupal_goto('admin/content/ad/groups/'. $group->tid .'/delete');
-  }
-
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Group name'),
-    '#default_value' => $group->name,
-    '#maxlength' => 64,
-    '#required' => TRUE,
-    '#description' => t('Specify a name for the ad group.')
-  );
-
-  $form['description'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Description'),
-    '#default_value' => $group->description,
-    '#required' => TRUE,
-    '#description' => t('Describe this ad group.')
-  );
-
-  $form['parent']['#tree'] = FALSE;
-
-  $form['weight'] = array(
-    '#type' => 'weight',
-    '#title' => t('Weight'),
-    '#default_value' => $group->weight,
-    '#description' => t('When listing ad groups, those with lighter (smaller) weights get listed before ad groups with heavier (larger) weights.  Ad groups with equal weights are sorted alphabetically.')
-  );
-
-  $form['vid'] = array(
-    '#type' => 'hidden',
-    '#value' => _ad_get_vid());
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Submit'));
-
-  if ($group->tid) {
-    $form['delete'] = array(
-      '#type' => 'submit',
-      '#value' => t('Delete'));
-    $form['tid'] = array(
-      '#type' => 'value',
-      '#value' => $group->tid);
-  }
-
-  return $form;
-}
-
-/**
- * Save a newly created ad group.
- */
-function ad_admin_group_form_submit($form, &$form_state) {
-  $status = taxonomy_save_term($form_state['values']);
-  switch ($status) {
-    case SAVED_NEW:
-      $groups = variable_get('ad_groups', array());
-      $groups[] = $form_state['values']['tid'];
-      variable_set('ad_groups', $groups);
-      drupal_set_message(t('Created new ad group %term.', array('%term' => $form_state['values']['name'])));
-      break;
-    case SAVED_UPDATED:
-      drupal_set_message(t('The ad group %term has been updated.', array('%term' => $form_state['values']['name'])));
-  }
-  $form_state['redirect'] = 'admin/content/ad/groups';
-}
-
-/**
- * Returns a confirmation page when deleting an ad group and all of its ads.
- */
-function ad_confirm_group_delete($form_state, $group = NULL) {
-  $form['tid'] = array('#type' => 'value', '#value' => $group->tid);
-  $form['name'] = array('#type' => 'value', '#value' => $group->name);
-
-  return confirm_form(
-    $form,
-    t('Are you sure you want to delete the ad group %name?', array('%name' => $group->name)),
-    'admin/content/ad/group',
-    t('Ads that were within this group will not be deleted.  This action cannot be undone.'),
-    t('Delete'),
-    t('Cancel'));
-}
-
-/**
- * Delete ad group.
- */
-function ad_confirm_group_delete_submit($form, &$form_state) {
-  taxonomy_del_term($form_state['values']['tid']);
-  drupal_set_message(t('The ad group %term has been deleted.', array('%term' => $form_state['values']['name'])));
-  watchdog('ad', 'mailarchive: deleted %term ad group.', array('%term' => $form_state['values']['name']));
-
-  $form_state['redirect'] = 'admin/content/ad/groups';
-}
-
-/**
  * Builds the necessary HTML to display an image-based view counter.
  */
 function ad_display_image($ad, $css = TRUE) {
@@ -2958,7 +1442,7 @@
   if ($cache != 'none') {
     $variables .= '&amp;c='. $cache . module_invoke('ad_cache_'. $cache, 'adcacheapi', 'display_variables', array());
   }
-  $output = '<img src="'. url($base_url. '/'. $adserve. $variables) .'" height="0" width="0" alt="view counter" />';
+  $output = '<img src="'. url($base_url .'/'. $adserve . $variables) .'" height="0" width="0" alt="view counter" />';
   if ($css) {
     return '<div class="ad-image-counter">'. $output .'</div>';
   }
@@ -2989,51 +1473,3 @@
   return $groups[$nid];
 }
 
-/**
- * Retrieve the relative path between folders.  Basically the opposite of the
- * realpath() function.
- */
-function _ad_relative_path($start_dir, $final_dir, $dirsep = DIRECTORY_SEPARATOR) {
-
-  // Directory separator consistency
-  $start_dir = str_replace('/',$dirsep,$start_dir);
-  $final_dir = str_replace('/',$dirsep,$final_dir);
-  $start_dir = str_replace('\\',$dirsep,$start_dir);
-  $final_dir = str_replace('\\',$dirsep,$final_dir);
-
-  // Explode
-  $firstPathParts = explode($dirsep, $start_dir);
-  $secondPathParts = explode($dirsep, $final_dir);
-
-  // Get the number of parts that are the same.
-  $sameCounter = 0;
-  for ($i = 0; $i < min (count($firstPathParts), count($secondPathParts)); $i++) {
-    if (strtolower($firstPathParts[$i]) !== strtolower($secondPathParts[$i])) {
-      break;
-    }
-    $sameCounter++;
-  }
-  // If they do not share any common directories/roots, just return 2nd path.
-  if ($sameCounter == 0) {
-    return $final_dir;
-  }
-  $newPath = '';
-  // Go up the directory structure count(firstpathparts)-sameCounter times 
-  // (so, go up number of non-matching parts in the first path.)
-  for ($i = $sameCounter; $i < count($firstPathParts); $i++) {
-    if ($i > $sameCounter) {
-      $newPath .= $dirsep;
-    }
-    $newPath .= "..";
-  }
-  // If we did not have to go up at all, we're still in start_dir.
-  if (strlen($newPath) == 0) {
-    $newPath = ".";
-  }
-  // Now we go down as much as needed to get to final_dir.
-  for ($i = $sameCounter; $i < count($secondPathParts); $i++) {
-    $newPath .= $dirsep;
-    $newPath .= $secondPathParts[$i];
-  }
-  return $newPath;
-}
Index: adserve.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/Attic/adserve.inc,v
retrieving revision 1.1.2.31
diff -u -r1.1.2.31 adserve.inc
--- adserve.inc	11 Aug 2008 17:37:17 -0000	1.1.2.31
+++ adserve.inc	27 Jan 2009 13:52:18 -0000
@@ -2,6 +2,7 @@
 // $Id: adserve.inc,v 1.1.2.31 2008/08/11 17:37:17 jeremy Exp $
 
 /**
+ * @file
  * Configuration.
  *
  * By default, adserve configuration happens dynamically as ads are served.
@@ -38,10 +39,10 @@
   $output = NULL;
   if (adserve_variable('adcache') != 'none') {
     /**
-     * Ad caches are defined through external modules.  Ad caches are composed 
-     * of a module 'ad_cache_TYPE.module' and an include file 
-     * 'ad_cache_TYPE.inc' that live in the 'cache/TYPE' subdirectory where 
-     * 'TYPE' is replaced with the type of cache.  For example, the included 
+     * Ad caches are defined through external modules.  Ad caches are composed
+     * of a module 'ad_cache_TYPE.module' and an include file
+     * 'ad_cache_TYPE.inc' that live in the 'cache/TYPE' subdirectory where
+     * 'TYPE' is replaced with the type of cache.  For example, the included
      * file cache lives in 'cache/file'.
      *
      * The ad_cache_TYPE.inc file must have a function named ad_cache_TYPE()
@@ -64,23 +65,23 @@
   }
 
   // If there's no output, we assume either there's no cache enabled, or the
-  // cache failed.  
+  // cache failed.
   // TODO: Log failures with the watchdog.
   if ($output == NULL) {
     if (adserve_variable('debug')) {
       echo "No cache enabled.<br />\n";
     }
-  
+
     adserve_bootstrap();
-    
+
     if (adserve_variable('nids')) {
       $id = adserve_variable('nids');
       $type = 'nids';
       adserve_variable('group', "n$id");
-  
+
       // Retrieve all active advertisements from the provided nid list.
-      $sql = "SELECT aid FROM {ads} WHERE adstatus = 'active' AND aid IN($id)";
-      $result = db_query($sql);
+      $sql = "SELECT aid FROM {ads} WHERE adstatus = 'active' AND aid IN (%s)";
+      $result = db_query($sql, $id);
 
       if (adserve_variable('debug')) {
         echo "Searching for ad from nid list: $id.<br />\n";
@@ -93,8 +94,8 @@
       adserve_variable('group', "t$id");
 
       // Retrieve all active advertisements from the provided tid list.
-      $sql = "SELECT a.aid FROM {ads} a INNER JOIN {term_node} n ON a.aid = n.nid WHERE a.adstatus = 'active' AND n.tid IN($id)";
-      $result = db_query($sql);
+      $sql = "SELECT a.aid FROM {ads} a INNER JOIN {term_node} n ON a.aid = n.nid WHERE a.adstatus = 'active' AND n.tid IN (%s)";
+      $result = db_query($sql, $id);
 
       if (adserve_variable('debug')) {
         echo "Searching for ad from tid list: $id.<br />\n";
@@ -133,7 +134,7 @@
 
     // Randomly select from available advertisements.
     $selected = adserve_select_ad($available, adserve_variable('quantity'));
-      
+
     $output = '';
     $ads = 0;
     $details = array();
@@ -143,7 +144,7 @@
       $ads++;
       $detail = $details[$aid] = node_load($aid);
       if (!isset($modules[$detail->adtype])) {
-        $modules[$detail->adtype] = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s'", "ad_$detail->adtype"));
+        $modules[$detail->adtype] = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s'", 'ad_'. $detail->adtype));
       }
       if (adserve_variable('debug')) {
         echo 'ad: <pre>';
@@ -152,9 +153,9 @@
         echo "Loading module '". $modules[$detail->adtype] ."'.<br />\n";
       }
       include_once $modules[$detail->adtype];
-      
+
       if ($output) {
-        // Add a div between ads that themers can use to arrange ads when 
+        // Add a div between ads that themers can use to arrange ads when
         // displaying more than one at a time.
         $displayed_count++;
         $output .= "<div class=\"advertisement-space\" id=\"space-$id-$displayed_count\"></div>";
@@ -196,7 +197,7 @@
 
   /**
    * Modules can add custom code to be displayed before or after ads are
-   * displayed.  For example, you many want to add a tagline, "Powered by 
+   * displayed.  For example, you many want to add a tagline, "Powered by
    * Drupal".  To do so, define 'adserve_exit_text' within your module's
    * adapi hook.
    *
@@ -211,7 +212,7 @@
    *       );
    *   }
    *
-   * As another example use case, you could also use the _init_text and 
+   * As another example use case, you could also use the _init_text and
    * _exit_text hooks to wrap all advertisements in a custom div.
    */
   $init = TRUE;
@@ -238,7 +239,7 @@
     case 'iframe':
     case 'jquery':
       if (!adserve_variable('debug')) {
-        // Tell the web browser not to cache this frame so the ad refreshes 
+        // Tell the web browser not to cache this frame so the ad refreshes
         // each time the page is viewed.
 
         // Expires in the past:
@@ -247,7 +248,7 @@
         header('Last-Modified: '. gmdate('D, d M Y H:i:s') .' GMT');
         // HTTP 1.1:
         header('Cache-Control: no-store, no-cache, must-revalidate');
-        header('Cache-Control: post-check=0, pre-check=0', false);
+        header('Cache-Control: post-check=0, pre-check=0', FALSE);
         // HTTP 1.0:
         header('Pragma: no-cache');
       }
@@ -259,7 +260,7 @@
                             array('\r', '\n', '\x3c', '\x3e', '\x26'),
                             addslashes($output));
       if (!adserve_variable('debug')) {
-        // Tell the web browser not to cache this script so the ad refreshes 
+        // Tell the web browser not to cache this script so the ad refreshes
         // each time the page is viewed.
         // Expires in the past:
         header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
@@ -267,7 +268,7 @@
         header('Last-Modified: '. gmdate('D, d M Y H:i:s') .' GMT');
         // HTTP 1.1:
         header('Cache-Control: no-store, no-cache, must-revalidate');
-        header('Cache-Control: post-check=0, pre-check=0', false);
+        header('Cache-Control: post-check=0, pre-check=0', FALSE);
         // HTTP 1.0:
         header('Pragma: no-cache');
         // Output is a JavaScript:
@@ -368,12 +369,12 @@
         if ($variables->debug) {
           echo "Attempting to include cache include file '$include'.<br />\n";
         }
-        require_once ($include);
+        require_once($include);
       }
       else if ($variables->debug) {
         echo "Failed to find cache include file '$include'.<br />\n";
       }
-      $function = "ad_cache_$variables->adcache". '_variables';
+      $function = 'ad_cache_'. $variables->adcache .'_variables';
       if (function_exists($function)) {
         $external_variables = $function();
         foreach ($external_variables as $key => $val) {
@@ -397,7 +398,7 @@
 
   if (isset($variables->$variable)) {
     return $variables->$variable;
-  } 
+  }
   else {
     return NULL;
   }
@@ -529,7 +530,7 @@
  */
 
 /**
- * Simple default function to randomly select an ad.  Provides a hook to allow 
+ * Simple default function to randomly select an ad.  Provides a hook to allow
  * the definition of external display methods.
  * @param An array of valid ad IDs, ie array(5, 8, 9, 11).
  * @param Optional, how many unique ads to select.
@@ -645,13 +646,13 @@
   // If column doesn't already exist, we need to add it.
   if (!db_affected_rows()) {
     db_query("INSERT INTO {ad_statistics} (aid, date, action, adgroup, hostid, count) VALUES(%d, %d, '%s', '%s', '%s', 1)", $ad->aid, date('YmdH'), $action, adserve_variable('hostid'), adserve_variable('hostid'));
-    // If another process already added this row our INSERT will fail, if 
+    // If another process already added this row our INSERT will fail, if
     // so we still need to increment it so we don't loose a view.
     if (!db_affected_rows()) {
       db_query("UPDATE {ad_statistics} SET count = count + 1 WHERE aid = %d AND action = '%s' AND date = %d AND adgroup = '%s' AND hostid = '%s'", $ad->aid, $action, date('YmdH'), adserve_variable('group'), adserve_variable('hostid'));
     }
   }
-  
+
   if ($action == 'view') {
     // See if we need to perform additional queries.
     if ($ad->maxviews) {
Index: imageserve.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/Attic/imageserve.inc,v
retrieving revision 1.1.2.8
diff -u -r1.1.2.8 imageserve.inc
--- imageserve.inc	16 Jan 2008 20:25:53 -0000	1.1.2.8
+++ imageserve.inc	27 Jan 2009 13:53:05 -0000
@@ -2,7 +2,12 @@
 // $Id: imageserve.inc,v 1.1.2.8 2008/01/16 20:25:53 jeremy Exp $
 
 /**
- * Generate a tiny image with GD, used to count when an ad has been displayed 
+ * @file
+ * Image serving lib.
+ */
+
+/**
+ * Generate a tiny image with GD, used to count when an ad has been displayed
  * on a cached page.
  */
 function adserve_counter_image() {
@@ -27,7 +32,7 @@
     header('Last-Modified: '. gmdate('D, d M Y H:i:s') .' GMT');
     // HTTP 1.1:
     header('Cache-Control: no-store, no-cache, must-revalidate');
-    header('Cache-Control: post-check=0, pre-check=0', false);
+    header('Cache-Control: post-check=0, pre-check=0', FALSE);
     // HTTP 1.0:
     header('Pragma: no-cache');
   }
Index: cache/file/ad_cache_file.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/cache/file/Attic/ad_cache_file.inc,v
retrieving revision 1.1.4.23
diff -u -r1.1.4.23 ad_cache_file.inc
--- cache/file/ad_cache_file.inc	11 Jul 2008 02:10:12 -0000	1.1.4.23
+++ cache/file/ad_cache_file.inc	27 Jan 2009 13:49:12 -0000
@@ -22,7 +22,7 @@
   _debug_echo('File cache: entering.');
 
   $cache_file = ad_cache_file_get_lock();
-   
+
   $output = '';
   if ($cache_file) {
     // Read entire cache file into memory.
@@ -127,7 +127,7 @@
           $aids = $cache['tid'][0]['aid'];
         }
       }
-  
+
       if (adserve_variable('debug')) {
         echo "File cache: last sync: $last_sync<br />\n";
         echo "File cache: current time: $time<br />\n";
@@ -164,24 +164,24 @@
       foreach ($selected as $aid) {
         $aid = (int)$aid;
         $ad = $cache['ad'][$aid];
-  
+
         if (!empty($output)) {
-          // Add a div between ads that themers can use to arrange ads when 
+          // Add a div between ads that themers can use to arrange ads when
           // displaying more than one at a time.
           $displayed_count++;
           $output .= "<div class=\"advertisement-space\" id=\"$id-$displayed_count\"></div>";
         }
         $output .= $ad['display'];
-       
+
         _debug_echo("File cache: displaying AID: $aid");
 
         // If displaying an ad, increment appropriate view counter.  Otherwise,
         // simply increment a counter.
         $action = $aid ? 'view' : 'count';
-  
+
         // Increment counter.
-        if (isset($cache['ad'][$aid][$hostid]) && 
-            isset($cache['ad'][$aid][$hostid]['counts'][$action]) && 
+        if (isset($cache['ad'][$aid][$hostid]) &&
+            isset($cache['ad'][$aid][$hostid]['counts'][$action]) &&
             isset($cache['ad'][$aid][$hostid]['counts'][$action][$timestamp])) {
           $cache['ad'][$aid][$hostid]['counts'][$action][$timestamp]++;
         }
@@ -200,7 +200,7 @@
       flock(adserve_variable('fd'), LOCK_UN);
       fclose(adserve_variable('fd'));
       adserve_variable('fd', '');
-  
+
       // Every $lifetime seconds we flush the cache files to the database.
       if ($last_sync < time() - $lifetime) {
         ad_cache_file_rebuild();
@@ -210,7 +210,7 @@
   else {
     $output = 'Configuration error, failed to lock cache file.';
     if (ad_cache_file_rebuild()) {
-      // Required function was missing, the file cache must be disabled, so 
+      // Required function was missing, the file cache must be disabled, so
       // return nothing allowing adserve.inc to use the default display method.
       return;
     }
@@ -234,16 +234,16 @@
     return $cache_file;
   }
 
-  // We'll loop through all possible cache files until we obtain an 
+  // We'll loop through all possible cache files until we obtain an
   // exclusive lock.
   for ($i = 1; $i <= adserve_variable('files'); $i++) {
     // Prefix the filename with a '.' to hide it on Unix systems.
     $cache_file = adserve_variable('root_dir') .'/'. adserve_variable('path') .'/.'. $i .'.ad.cache';
-  
+
     if (adserve_variable('debug')) {
       echo "Trying cache_file '$cache_file'.<br />\n";
     }
-  
+
     if (!$fd = @fopen($cache_file, 'r+')) {
       if (adserve_variable('debug')) {
         echo "Failed to open cache_file '$cache_file'.<br />\n";
@@ -252,9 +252,9 @@
       continue;
     }
     if ($i < adserve_variable('files')) {
-      // This isn't the last available cache file so we'll use a 
-      // non-blocking lock for best performance.  If we fail to lock this 
-      // cache file, we'll quickly move on to the next until we find an 
+      // This isn't the last available cache file so we'll use a
+      // non-blocking lock for best performance.  If we fail to lock this
+      // cache file, we'll quickly move on to the next until we find an
       // available one.
       if (!flock($fd, LOCK_EX|LOCK_NB)) {
         if (adserve_variable('debug')) {
@@ -274,7 +274,7 @@
       break;
     }
     else {
-      // This is the last available cache file, we'll use a blocking lock 
+      // This is the last available cache file, we'll use a blocking lock
       // as we have to wait until we have exclusive write permissions.
       if (!flock($fd, LOCK_EX)) {
         if (adserve_variable('debug')) {
@@ -323,15 +323,15 @@
     $timestamp = date('YmdH');
 
     // Increment action counter.
-    if (isset($cache['ad'][$aid][$hostid]) && 
-        isset($cache['ad'][$aid][$hostid]['counts'][$action]) && 
+    if (isset($cache['ad'][$aid][$hostid]) &&
+        isset($cache['ad'][$aid][$hostid]['counts'][$action]) &&
         isset($cache['ad'][$aid][$hostid]['counts'][$action][$timestamp])) {
       $cache['ad'][$aid][$hostid]['counts'][$action][$timestamp]++;
     }
     else {
       $cache['ad'][$aid][$hostid]['counts'][$action][$timestamp] = 1;
     }
-  
+
     // Write updated cache back to file and release the lock.
     $cache = serialize($cache);
     rewind(adserve_variable('fd'));
@@ -355,7 +355,7 @@
  *  Additional variables required by the filecache.
  */
 function ad_cache_file_variables() {
-  // paths are comprised of alphanumerics, underscores, dashes, periods and 
+  // paths are comprised of alphanumerics, underscores, dashes, periods and
   // slashes.
   $variables = array();
   $variables['path'] = isset($_GET['p']) ? preg_replace('/[^_\-\.\/\\0-9a-zA-Z]/', '', $_GET['p']) : 'files';
@@ -369,7 +369,7 @@
 }
 
 /**
- * Bootstrap drupal, then run ad_cache_file_build() from ad.module which will 
+ * Bootstrap drupal, then run ad_cache_file_build() from ad.module which will
  * rebuild all cache files.
  */
 function ad_cache_file_rebuild() {
@@ -418,5 +418,3 @@
     _debug_echo("File cache: adserve_select: no select function defined");
   }
 }
-
-?>
Index: cache/file/ad_cache_file.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/cache/file/Attic/ad_cache_file.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_cache_file.info
--- cache/file/ad_cache_file.info	11 Aug 2008 21:15:26 -0000	1.1.2.1.2.1
+++ cache/file/ad_cache_file.info	22 Jan 2009 14:56:26 -0000
@@ -1,6 +1,12 @@
 ; $Id: ad_cache_file.info,v 1.1.2.1.2.1 2008/08/11 21:15:26 jeremy Exp $
-name = File cache
+name = Ad file cache
 package = Ad
 dependencies[] = ad
 description = Provides a file-caching mechanism to improve ad serving performance.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: cache/file/ad_cache_file.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/cache/file/Attic/ad_cache_file.module,v
retrieving revision 1.1.4.18.2.1
diff -u -r1.1.4.18.2.1 ad_cache_file.module
--- cache/file/ad_cache_file.module	11 Aug 2008 21:15:26 -0000	1.1.4.18.2.1
+++ cache/file/ad_cache_file.module	26 Jan 2009 14:03:08 -0000
@@ -5,13 +5,10 @@
  * @file
  * A plug in for the ad.module, providing a file cache mechanism for improved
  * performance when displaying ads.
- *
- * Copyright (c) 2007-2008.
- *  Jeremy Andrews <jeremy@kerneltrap.org>.  All rights reserved.
  */
 
 /**
- * Drupal _help hook.
+ * Implementation of hook_help().
  */
 function ad_cache_file_help($path, $arg) {
   switch ($path) {
@@ -24,7 +21,7 @@
 }
 
 /**
- * Ad module's adcacheapi _hook().
+ * Implementation of hook_adcacheapi().
  */
 function ad_cache_file_adcacheapi($op, &$node) {
   switch ($op) {
@@ -228,5 +225,4 @@
   $cache['last_sync'] = time();
   $cache['lifetime'] = variable_get('ad_cache_file_lifetime', 60);
   return $cache;
-}
-
+}
\ No newline at end of file
Index: cache/memcache/ad_cache_memcache.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/cache/memcache/Attic/ad_cache_memcache.inc,v
retrieving revision 1.1.2.6.2.1
diff -u -r1.1.2.6.2.1 ad_cache_memcache.inc
--- cache/memcache/ad_cache_memcache.inc	11 Aug 2008 21:15:26 -0000	1.1.2.6.2.1
+++ cache/memcache/ad_cache_memcache.inc	27 Jan 2009 13:45:06 -0000
@@ -1,4 +1,10 @@
 <?php
+// $Id$
+
+/**
+ * @file
+ * Memcache include.
+ */
 
 /**
  * TODO: Debug Raw and IFrame display methods, neither currently seem to work
@@ -11,7 +17,7 @@
 function ad_cache_memcache() {
   _debug_echo('Memcache: entering ad_cache_memcache().');
 
-  // TODO: Move the meat of this function into adserve.php, simplifying what 
+  // TODO: Move the meat of this function into adserve.php, simplifying what
   // cache plugins have to do and removing duplicated logic.
   $init_cache = array();
   $init_func = ad_cache_memcache_hook($init_cache, 'include_file_init', 'include_func_init');
@@ -194,7 +200,7 @@
     return (0);
   }
 
-  if (is_null($timestamp)) {
+  if (!isset($timestamp)) {
     $timestamp = date('YmdH');
   }
   $counters = ad_memcache_get("ad-counters-$aid");
@@ -324,8 +330,7 @@
   $key = "ad-counters-$aid";
   if (ad_memcache_lock($key)) {
     $counters = ad_memcache_get($key);
-    if (!is_array($counters) || 
-        !isset($counters["$action:$group:$hostid:$timestamp"])) {
+    if (!is_array($counters) || !isset($counters["$action:$group:$hostid:$timestamp"])) {
       $counters["$action:$group:$hostid:$timestamp"] = "$action:$group:$hostid:$timestamp";
       ad_memcache_set($key, $counters);
     }
Index: cache/memcache/ad_cache_memcache.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/cache/memcache/Attic/ad_cache_memcache.info,v
retrieving revision 1.1.2.2.2.1
diff -u -r1.1.2.2.2.1 ad_cache_memcache.info
--- cache/memcache/ad_cache_memcache.info	11 Aug 2008 21:15:26 -0000	1.1.2.2.2.1
+++ cache/memcache/ad_cache_memcache.info	20 Nov 2008 00:00:38 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = ad
 description = Integrates the ad module with memcached, allowing ads to be served directly out of RAM.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: cache/memcache/ad_cache_memcache.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/cache/memcache/Attic/ad_cache_memcache.module,v
retrieving revision 1.1.2.9.2.1
diff -u -r1.1.2.9.2.1 ad_cache_memcache.module
--- cache/memcache/ad_cache_memcache.module	11 Aug 2008 21:15:26 -0000	1.1.2.9.2.1
+++ cache/memcache/ad_cache_memcache.module	26 Jan 2009 14:02:18 -0000
@@ -4,11 +4,11 @@
 /**
  * @file
  * A plug in for the ad.module, integrating the ad module with memcache.
- *
- * Copyright (c) 2008.
- *  Jeremy Andrews <jeremy@tag1consulting.com>.
  */
 
+/**
+ * Implementation of hook_requirements().
+ */
 function ad_cache_memcache_requirements($phase = NULL) {
   // Connect to memcached so we can retrieve its version.
   if (function_exists('memcache_add_server')) {
@@ -49,7 +49,7 @@
 }
 
 /**
- * Drupal _help hook.
+ * Implementation of hook_help().
  */
 function ad_cache_memcache_help($path, $arg) {
   switch ($path) {
@@ -62,7 +62,7 @@
 }
 
 /**
- * Ad module's adcacheapi _hook().
+ * Implementation of hook_adcacheapi().
  */
 function ad_cache_memcache_adcacheapi($op, &$node) {
   switch ($op) {
Index: documentation/CHANGELOG.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/documentation/CHANGELOG.txt,v
retrieving revision 1.2.2.40.2.176.2.10
diff -u -r1.2.2.40.2.176.2.10 CHANGELOG.txt
--- documentation/CHANGELOG.txt	26 Jan 2009 03:17:58 -0000	1.2.2.40.2.176.2.10
+++ documentation/CHANGELOG.txt	22 Jan 2009 12:51:54 -0000
@@ -1,13 +1,4 @@
-$Id: CHANGELOG.txt,v 1.2.2.40.2.176.2.10 2009/01/26 03:17:58 jeremy Exp $
-
-November 25th, 2008
- - ad.module
-    o cleanup Drupal 6 port
-    o bug #355662: rename _drupal_add_js() to drupal_add_js()
- - ad_html.module
-    o cleanup Drupal 6 port
- - click_filter.module
-    o cleanup Drupal 6 port
+$Id: CHANGELOG.txt,v 1.2.2.40.2.176.2.8 2008/11/19 19:29:57 jeremy Exp $
 
 November 19th, 2008
  - ad.module
Index: documentation/ad_types.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/documentation/ad_types.txt,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 ad_types.txt
--- documentation/ad_types.txt	31 Dec 2006 02:28:03 -0000	1.1.2.1
+++ documentation/ad_types.txt	22 Jan 2009 13:35:14 -0000
@@ -2,39 +2,46 @@
 Overview:
 ---------
 This document is intended to help someone write a module for the ad api to
-introduce a new ad type.  The core ad module includes two ad type modules, one
-for text ads and another for image ads.  You can use the same api used by these
+introduce a new ad type. The core ad module includes two ad type modules, one
+for text ads and another for image ads. You can use the same api used by these
 two ad type modules to create your own custom ad type module, for example you
-may wish to write a module to ad support for flash ads.  Some familiarity with
+may wish to write a module to ad support for flash ads. Some familiarity with
 Drupal and PHP is required.
 
 --------------------------
 Naming your new ad module:
 --------------------------
 There are two ad types included with the core ad module, text ads and image
-ads.  Each ad type lives in its own module.  Text ads are defined in the
-ad_text module, and image ads are defined in the ad_image module.  All
+ads. Each ad type lives in its own module. Text ads are defined in the
+ad_text module, and image ads are defined in the ad_image module. All
 additional types of ads should be defined in modules following the same naming
-scheme which is 'ad_' followed by the type of ad.  For example, if you are
+scheme which is 'ad_' followed by the type of ad. For example, if you are
 creating a module to add support for flash-based ads, you would call your
 module ad_flash.
 
 ------------------------------
 Registering a new style of ad:
 ------------------------------
-Within the Drupal framework, ads are nodes.  To create a new ad, a user
+Within the Drupal framework, ads are nodes. To create a new ad, a user
 navigates to "create content >> ad", on which page they will be prompted
-to select the type of the ad they wish to create.  Your new ad type can
-be added to this list by using the _adapi() hook and the 'type' operator.  
+to select the type of the ad they wish to create. Your new ad type can
+be added to this list by using the _adapi() hook and the 'type' operator. 
 For example, if creating a module for flash ads, you might add the following 
 function:
 
-  ad_flash_adapi($op, $ad = NULL) {
-    switch ($op) {
-      case 'type':
-        return t('flash');
-    }
+ad_flash_adapi($op, $ad = NULL) {
+  switch ($op) {
+    case 'type':
+      return array(
+        'flash' => array(
+          'name' => t('Flash ad'),
+          'module' => 'ad_flash',
+          'description' => t('A flash advertisement.'),
+          'help' => t('A flash advertisement.'),
+        ),
+      );
   }
+}
 
 ------------------
 Displaying your ad
@@ -45,19 +52,19 @@
 DRUPAL_BOOTSTRAP_DATABASE.
 
 You are passed in an object which contains the ad ID (aid) and the redirect
-URL (ie 'ad/redirect/13').  Utilize this information to return the complete
-advertisement.  For example:
+URL (ie 'ad/redirect/13'). Utilize this information to return the complete
+advertisement. For example:
 
-  function ad_static_display_ad($ad) {
-    $return "<a href=\"$ad->redirect\">Static ad #$ad->aid</a>";
-  }
+function ad_static_display_ad($ad) {
+  $return "<a href=\"$ad->redirect\">Static ad #$ad->aid</a>";
+}
 
 --------------------------------
 Sharing your new ad_type module:
 --------------------------------
 The drupal.org CVS contributions repository requires that all included modules
-be licesensed under the GPL.  The ad module is dual licensed under both the
-GPL and the BSD license, meeting this requirement.  You can license your
+be licesensed under the GPL. The ad module is dual licensed under both the
+GPL and the BSD license, meeting this requirement. You can license your
 new ad_style module under just the GPL, or under a dual license as long as
 one of the licenses is the GPL.
 
Index: embed/ad_embed.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/embed/Attic/ad_embed.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_embed.info
--- embed/ad_embed.info	11 Aug 2008 21:15:26 -0000	1.1.2.1.2.1
+++ embed/ad_embed.info	20 Nov 2008 00:00:38 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = ad
 description = Embed ads in content.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: embed/ad_embed.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/embed/Attic/ad_embed.module,v
retrieving revision 1.1.2.4.2.12.2.1
diff -u -r1.1.2.4.2.12.2.1 ad_embed.module
--- embed/ad_embed.module	11 Aug 2008 21:15:26 -0000	1.1.2.4.2.12.2.1
+++ embed/ad_embed.module	26 Jan 2009 13:45:09 -0000
@@ -4,9 +4,6 @@
 /**
  * @file
  * Embed ads in content.
- *
- * Copyright (c) 2007-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.  All rights reserved.
  */
 
 /**
@@ -44,7 +41,7 @@
 }
 
 /**
- * Drupal _perm hook.
+ * Implementation of hook_perm().
  */
 function ad_embed_perm() {
   return array('embed ads in content');
@@ -68,10 +65,9 @@
 }
 
 /**
- * Drupal _nodeapi hook.
+ * Implementation of hook_nodeapi().
  */
 function ad_embed_nodeapi(&$node, $op, $teaser, $page) {
-
   switch ($op) {
     case 'view':
       $node = (object)$node;
@@ -94,7 +90,7 @@
 /**
  * Module settings page.
  */
-function ad_embed_admin_configure_settings() {
+function ad_embed_admin_configure_settings($form_state) {
   $form = array();
 
   $form['manual'] = array(
@@ -271,7 +267,7 @@
                 break;
             }
           }
-          $replace = "$open$contents$close";
+          $replace = $open . $contents . $close;
           $ad = ad($group, $quantity, $options);
           $text = str_replace($replace, $ad, $text);
           // Adjust position to compensate for difference in length of what 
@@ -310,7 +306,7 @@
       if ($paragraph == $count) {
         $part1 = substr($text, 0, $pos);
         $part2 = substr($text, $pos + 1, strlen($text));
-        $text = "$part1$ad$part2";
+        $text = $part1 . $ad . $part2;
         break;
       }
     }
Index: external/ad_external.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/external/Attic/ad_external.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_external.info
--- external/ad_external.info	11 Aug 2008 21:15:26 -0000	1.1.2.1.2.1
+++ external/ad_external.info	20 Nov 2008 00:00:38 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = ad
 description = Enhances the ad module to support externally hosted ads.  Typically combined with IFrames.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: external/ad_external.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/external/Attic/ad_external.install,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 ad_external.install
--- external/ad_external.install	19 Oct 2007 00:36:36 -0000	1.1.2.1
+++ external/ad_external.install	27 Jan 2009 13:44:39 -0000
@@ -2,31 +2,46 @@
 // $Id: ad_external.install,v 1.1.2.1 2007/10/19 00:36:36 jeremy Exp $
 
 /**
+ * @file
  * Ad_external module database schema.
- * Copyright (c) 2007 Jeremy Andrews <jeremy@kerneltrap.org>.  
- * All rights reserved.
  */
 
-function ad_external_install() {
-  switch ($GLOBALS['db_type']) {
-    case 'mysql':
-    case 'mysqli':
-    default:
-      db_query("CREATE TABLE {ad_external} (
-        aid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-
-        url VARCHAR(255) NOT NULL DEFAULT '',
+/**
+ * Implementation of hook_schema().
+ */
+function ad_external_schema() {
+  $schema['ad_external'] = array(
+    'description' => 'The ad_external table stores source URLs for external ads.',
+    'fields' => array(
+      'aid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'url' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      )
+    ),
+    'primary key' => array('aid'),
+  );
 
-        PRIMARY KEY (aid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-  }
+  return $schema;
+}
 
-  drupal_set_message(t('The necessary ad_external module tables have been created.'));
+/**
+ * ad_external module installation.
+ */
+function ad_external_install() {
+  drupal_install_schema('ad_external');
 }
 
 /**
  * Allow complete uninstallation of the ad_external module.
  */
 function ad_external() {
-  db_query('DROP TABLE {ad_external}');
+  drupal_uninstall_schema('ad_external');
 }
Index: external/ad_external.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/external/Attic/ad_external.module,v
retrieving revision 1.1.2.6.2.1
diff -u -r1.1.2.6.2.1 ad_external.module
--- external/ad_external.module	11 Aug 2008 21:15:26 -0000	1.1.2.6.2.1
+++ external/ad_external.module	26 Jan 2009 13:46:29 -0000
@@ -8,9 +8,6 @@
  *  Ads >> Settings >> Global settings >> Display type" to "Raw".
  * If you configure the "Display type" to IFrame, you will be serving IFrames
  * within IFrames.
- *
- * Copyright (c) 2007-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.  All rights reserved.
  */
 
 /**
@@ -58,6 +55,9 @@
   return $output;
 }
 
+/**
+ * Implementation of hook_adapi().
+ */
 function ad_external_adapi($op, &$node) {
   switch ($op) {
     case 'load':
@@ -87,7 +87,14 @@
       return;
 
     case 'type':
-      return 'external';
+      return array(
+        'external' => array(
+          'name' => t('External ad'),
+          'module' => 'ad_external',
+          'description' => t('An external advertisement, displayed in an IFrame.'),
+          'help' => t('An external advertisement, displayed in an IFrame.'),
+        ),
+      );
   }
 }
 
@@ -123,6 +130,9 @@
   return $form;
 }
 
+/**
+ * Adapi helper function for displaying ad itself.
+ */
 function ad_external_node_view(&$node) {
   $node->content['ad'] = array(
     '#value' => theme('box', '', stripslashes(ad_external_display_ad($node))),
@@ -157,5 +167,4 @@
     $cache['ad_external'][$external->aid]['contents'] = $contents;
   }
   return $cache;
-}
-
+}
\ No newline at end of file
Index: external/external.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/external/Attic/external.inc,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 external.inc
--- external/external.inc	2 Nov 2007 00:10:04 -0000	1.1.2.1
+++ external/external.inc	22 Jan 2009 14:39:32 -0000
@@ -1,4 +1,10 @@
 <?php
+// $Id$
+
+/**
+ * @file
+ * Loads external ad.
+ */
 
 adserve_variable('ad_dir', getcwd() .'/');
 
@@ -26,5 +32,3 @@
     }
   }
 }
-
-?>
Index: html/ad_html.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/html/Attic/ad_html.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_html.info
--- html/ad_html.info	11 Aug 2008 21:15:26 -0000	1.1.2.1.2.1
+++ html/ad_html.info	20 Nov 2008 00:00:38 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = ad
 description = Enhances the ad module to support html-based ads, such as those defined by many advertising affiliate websites.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: html/ad_html.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/html/Attic/ad_html.install,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_html.install
--- html/ad_html.install	11 Aug 2008 21:15:26 -0000	1.1.2.1.2.1
+++ html/ad_html.install	27 Jan 2009 13:44:33 -0000
@@ -2,45 +2,38 @@
 // $Id: ad_html.install,v 1.1.2.1.2.1 2008/08/11 21:15:26 jeremy Exp $
 
 /**
+ * @file
  * Ad_html module database schema.
- * Copyright (c) 2007 Jeremy Andrews <jeremy@kerneltrap.org>.  
- * All rights reserved.
  */
 
-function ad_html_install() {
-  switch ($GLOBALS['db_type']) {
-
-    case 'mysql':
-    case 'mysqli':
-    default:
-
-      /**
-       * The ad_html table stores the html ad.
-       */
-      db_query("CREATE TABLE {ad_html} (
-        aid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-
-        html TEXT NULL,
-
-        PRIMARY KEY (aid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-      break;
-
-    case 'pgsql':
-      /**
-       * The ad_html table stores the html ad.
-       */
-      db_query("CREATE TABLE {ad_html} (
-        aid INT NOT NULL DEFAULT '0' PRIMARY KEY,
-
-        html TEXT NOT NULL DEFAULT ''
-      );");
-      break;
-
-
-  }
+/**
+ * Implementation of hook_schema().
+ */
+function ad_html_schema() {
+  $schema['ad_html'] = array(
+    'description' => 'The ad_html table stores HTML code of html ads.',
+    'fields' => array(
+      'aid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'html' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('aid'),
+  );
+  return $schema;
+}
 
-  drupal_set_message(t('The necessary ad_html module tables have been created.'));
+/**
+ * ad_html module installation.
+ */
+function ad_html_install() {
+  drupal_install_schema('ad_html');
 }
 
 /**
@@ -55,6 +48,4 @@
   while ($aid = db_result($result)) {
     node_delete($aid);
   }
-
-  // Drop all ad_html module tables.
 }
Index: html/ad_html.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/html/Attic/ad_html.module,v
retrieving revision 1.1.2.5.2.2
diff -u -r1.1.2.5.2.2 ad_html.module
--- html/ad_html.module	26 Jan 2009 02:21:09 -0000	1.1.2.5.2.2
+++ html/ad_html.module	26 Jan 2009 13:14:31 -0000
@@ -1,14 +1,12 @@
 <?php
-// $Id: ad_html.module,v 1.1.2.5.2.2 2009/01/26 02:21:09 jeremy Exp $
+// $Id: ad_html.module,v 1.1.2.5.2.1 2008/08/11 21:15:26 jeremy Exp $
 
 /**
  * @file
  * Enhances the ad module to support html ads.
- *
- * Copyright (c) 2007-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.
  */
 
+
 /**
  * Function used to display the selected ad.
  */
@@ -60,12 +58,15 @@
     case 'view':
       return ad_html_node_view($node);
 
-    case 'redirect':
-      return db_result(db_query('SELECT url FROM {ad_html} WHERE aid = %d', $node->nid));
-
     case 'type':
-      return 'html';
-
+      return array(
+        'html' => array(
+          'name' => t('HTML ad'),
+          'module' => 'ad_html',
+          'description' => t('A html advertisement.'),
+          'help' => t('A html advertisement.'),
+        ),
+      );
     case 'permissions':
       if (!isset($node->adtype) || $node->adtype == 'html') {
         return array('manage ad html');
@@ -90,7 +91,7 @@
     '#value' => ad_html_display_ad($node),
   );
 
-  if (ad_adaccess($node->nid, 'manage ad html') ||
+  if (ad_adaccess($node, 'manage ad html') ||
       arg(1) == 'add' && user_access('create advertisements')) {
     $form['ad_html']['html'] = array(
       '#type' => 'textarea',
Index: image/ad_image.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/image/Attic/ad_image.info,v
retrieving revision 1.1.2.5.2.1
diff -u -r1.1.2.5.2.1 ad_image.info
--- image/ad_image.info	11 Aug 2008 21:15:26 -0000	1.1.2.5.2.1
+++ image/ad_image.info	20 Nov 2008 00:00:38 -0000
@@ -4,4 +4,10 @@
 dependencies[] = ad
 dependencies[] = upload
 description = Enhances the ad module to support banner ads.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: image/ad_image.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/image/ad_image.install,v
retrieving revision 1.2.2.2.2.6.2.1
diff -u -r1.2.2.2.2.6.2.1 ad_image.install
--- image/ad_image.install	11 Aug 2008 21:15:26 -0000	1.2.2.2.2.6.2.1
+++ image/ad_image.install	27 Jan 2009 13:44:23 -0000
@@ -2,78 +2,103 @@
 // $Id: ad_image.install,v 1.2.2.2.2.6.2.1 2008/08/11 21:15:26 jeremy Exp $
 
 /**
+ * @file
  * Ad_image module database schema.
- * Copyright (c) 2005-2006 Jeremy Andrews <jeremy@kerneltrap.org>.  
- * All rights reserved.
  */
 
-function ad_image_install() {
-  switch ($GLOBALS['db_type']) {
-    case 'pgsql':
-      /**
-      * The ad_image_format table provides format guidelines for a given group
-      * of image ads.
-      */
-      db_query("CREATE TABLE {ad_image_format} (
-        gid INT NOT NULL UNIQUE PRIMARY KEY,
-
-        min_width INT NOT NULL DEFAULT '0',
-        max_width INT NOT NULL DEFAULT '0',
-        min_height INT NOT NULL DEFAULT '0',
-        max_height INT NOT NULL DEFAULT '0'
-      );");
-
-      /**
-       * The ad_image table stores information about each image ad.
-       */
-      db_query("CREATE TABLE {ad_image} (
-        aid INT NOT NULL DEFAULT '0' UNIQUE,
-        fid INT NOT NULL DEFAULT '0',
- 
-        url VARCHAR(255) NOT NULL DEFAULT '',
-        tooltip VARCHAR(255) NOT NULL DEFAULT '',
-        width INT NOT NULL DEFAULT '0',
-        height INT NOT NULL DEFAULT '0'
-      );");
-      break;
-
-    case 'mysql':
-    case 'mysqli':
-    default:
-
-      /**
-       * The ad_image_format table provides format guidelines for a given group
-       * of image ads.
-       */
-      db_query("CREATE TABLE {ad_image_format} (
-        gid INT(10) UNSIGNED NOT NULL,
-
-        min_width INT(5) UNSIGNED NOT NULL DEFAULT '0',
-        max_width INT(5) UNSIGNED NOT NULL DEFAULT '0',
-        min_height INT(5) UNSIGNED NOT NULL DEFAULT '0',
-        max_height INT(5) UNSIGNED NOT NULL DEFAULT '0',
-
-        PRIMARY KEY (gid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-
-      /**
-       * The ad_image table stores information about each image ad.
-       */
-      db_query("CREATE TABLE {ad_image} (
-        aid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-        fid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-
-        url VARCHAR(255) NOT NULL DEFAULT '',
-        tooltip VARCHAR(255) NOT NULL DEFAULT '',
-        width INT UNSIGNED NOT NULL DEFAULT '0',
-        height INT UNSIGNED NOT NULL DEFAULT '0',
+/**
+ * Implementation of hook_schema().
+ */
+function ad_image_schema() {
+  $schema['ad_image'] = array(
+    'description' => 'The ad_image table stores image information such as file ID, title, width, height of corresponding image ads.',
+    'fields' => array(
+      'aid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'fid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'url' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'tooltip' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'width' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'height' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'unique keys' => array(
+      'aid' => array('aid')
+    ),
+  );
+  $schema['ad_image_format'] = array(
+    'description' => 'The ad_image_format table stores dimensions for image ads.',
+    'fields' => array(
+      'gid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'min_width' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'max_width' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'min_height' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'max_height' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('gid'),
+  );
 
-        UNIQUE KEY (aid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+  return $schema;
+}
 
-  }
 
-  drupal_set_message(t('The necessary ad_image module tables have been created.'));
+/**
+ * ad_image module installation.
+ */
+function ad_image_install() {
+  drupal_install_schema('ad_image');
 }
 
 /**
@@ -88,14 +113,4 @@
   while ($aid = db_result($result)) {
     node_delete($aid);
   }
-
-  // Drop all ad_image module tables.
-}
-
-/**
- * Ad tooltip support to images.
- */
-/**
- * Convert to utf8 character set for all tables to allow for proper 
- * internationalization.
- */
+}
\ No newline at end of file
Index: image/ad_image.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/image/ad_image.module,v
retrieving revision 1.2.2.13.2.40.2.1
diff -u -r1.2.2.13.2.40.2.1 ad_image.module
--- image/ad_image.module	11 Aug 2008 21:15:26 -0000	1.2.2.13.2.40.2.1
+++ image/ad_image.module	26 Jan 2009 14:00:11 -0000
@@ -4,9 +4,6 @@
 /**
  * @file
  * Enhances the ad module to support banner ads.
- *
- * Copyright (c) 2005-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.
  */
 
 /**
@@ -28,11 +25,9 @@
   return $ad_image;
 }
 
-/*****/
-
 /**
- * Drupal _init hook.  Include ad_image_views.inc if the views.module is 
- * enabled.
+ * Implementation of hook_init().
+ * Include ad_image_views.inc if the views.module is enabled.
  */
 function ad_image_init() {
   if (function_exists('drupal_set_content')) {
@@ -54,6 +49,9 @@
   return $output;
 }
 
+/**
+ * Image ad settings form.
+ */
 function ad_image_global_settings($edit = array()) {
   $form = array();
 
@@ -134,7 +132,7 @@
 }
 
 /**
- * Adapi implementation.
+ * Implementation of hook_adapi().
  */
 function ad_image_adapi($op, &$node) {
   $output = NULL;
@@ -153,7 +151,7 @@
       // to modify the upload module's form.  Instead, we check after the fact
       // if someone is editing images when they're not allowed, and if so we
       // prevent the ad from being saved.
-      if ($op == 'update' && !ad_adaccess($node->nid, 'manage active ad')) {
+      if ($op == 'update' && !ad_adaccess($node, 'manage active ad')) {
         // See if fid is changing -- it's okay if new images are uploaded, it's
         // just not okay if the active fid is changed.
         if ($fid != $image->fid) {
@@ -219,8 +217,14 @@
       return db_result(db_query('SELECT url FROM {ad_image} WHERE aid = %d', $node->nid));
 
     case 'type':
-      return 'image';
-
+      return array(
+        'image' => array(
+          'name' => t('Image ad'),
+          'module' => 'ad_image',
+          'description' => t('An image or banner advertisement.'),
+          'help' => t('An image or banner advertisement.'),
+        ),
+      );
     case 'permissions':
       if (!isset($node->adtype) || $node->adtype == 'image') {
         return array('manage active ad');
@@ -265,9 +269,12 @@
   return 0;
 }
 
+/**
+ * Loads image format object from DB.
+ */
 function ad_image_format_load($gid) {
   static $format;
-  if (!is_null($format[$gid])) {
+  if (isset($format[$gid])) {
     return $format[$gid];
   }
   $format[$gid] = db_fetch_object(db_query('SELECT * FROM {ad_image_format} WHERE gid = %d', $gid));
@@ -324,7 +331,7 @@
 }
 
 /**
- *
+ * Returns image object from given ad node.
  */
 function ad_image_load_image($node) {
   if (is_array($node->files)) {
@@ -433,6 +440,9 @@
   return $form;
 }
 
+/**
+ * Adapi helper function for displaying ad itself.
+ */
 function ad_image_node_view(&$node) {
   $node->content['ad'] = array(
     '#value' => ad_image_display_ad($node),
@@ -448,5 +458,4 @@
       '#weight' => 0,
     );
   }
-}
-
+}
\ No newline at end of file
Index: image/ad_image_views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/image/Attic/ad_image_views.inc,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 ad_image_views.inc
--- image/ad_image_views.inc	27 Jan 2007 19:59:41 -0000	1.1.2.1
+++ image/ad_image_views.inc	22 Jan 2009 14:35:56 -0000
@@ -62,5 +62,3 @@
     return $ad->filepath;
   }
 }
-
-?>
Index: notify/ad_notify.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/notify/Attic/ad_notify.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_notify.info
--- notify/ad_notify.info	11 Aug 2008 21:15:27 -0000	1.1.2.1.2.1
+++ notify/ad_notify.info	27 Jan 2009 12:04:00 -0000
@@ -1,5 +1,11 @@
 ; $Id: ad_notify.info,v 1.1.2.1.2.1 2008/08/11 21:15:27 jeremy Exp $
 name = Notify
 package = Ad
-dependencies[] = ad
-description = Receive email notifications regarding ads.core = 6.x
\ No newline at end of file
+dependencies[] = ad, ad_owners
+description = Receive email notifications regarding ads.core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: notify/ad_notify.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/notify/Attic/ad_notify.install,v
retrieving revision 1.1.2.2.2.6.2.1
diff -u -r1.1.2.2.2.6.2.1 ad_notify.install
--- notify/ad_notify.install	11 Aug 2008 21:15:27 -0000	1.1.2.2.2.6.2.1
+++ notify/ad_notify.install	27 Jan 2009 13:44:15 -0000
@@ -2,94 +2,128 @@
 // $Id: ad_notify.install,v 1.1.2.2.2.6.2.1 2008/08/11 21:15:27 jeremy Exp $
 
 /**
+ * @file
  * Ad_notify module database schema.
- * Copyright (c) 2007 Jeremy Andrews <jeremy@kerneltrap.org>
- * All rights reserved.
  */
-function ad_notify_install() {
-  switch ($GLOBALS['db_type']) {
-    case 'pgsql':
-    /**
-     * Notifications can be granted to each owner of each ad.  The same owner
-     * can own multiple ads, and can have different notifications for each
-     * ad.  Notifications are defined by their type and an offset in seconds.
-     * For example, 'day, 0' would send a notification at the start of 
-     * every day, and 'expire, -86400' would send a notification one day
-     * before the ad expires.
-     */
-    db_query("CREATE TABLE {ad_notify} (
-      notid SERIAL NOT NULL PRIMARY KEY,
-      aid INT NOT NULL DEFAULT '0',
-      oid INT NOT NULL DEFAULT '0',
-
-      event VARCHAR(255) NOT NULL DEFAULT '',
-      delay INT NOT NULL DEFAULT '0',
-      queued INT NOT NULL DEFAULT '0',
-      time INT NOT NULL DEFAULT '0',
-      sent INT NOT NULL DEFAULT '0',
-      counter INT NOT NULL DEFAULT '0',
-      locked INT NOT NULL DEFAULT '0',
-      expire INT NOT NULL DEFAULT '0',
-      status INT NOT NULL DEFAULT '0',
-
-      address VARCHAR(255) NOT NULL DEFAULT '',
-      subject VARCHAR(255) NOT NULL DEFAULT '',
-      body TEXT NOT NULL DEFAULT '',
-
-      UNIQUE (oid, event, delay)
-      );");
-      break;
 
-    case 'mysql':
-    case 'mysqli':
-    default:
-
-    /**
-     * Notifications can be granted to each owner of each ad.  The same owner
-     * can own multiple ads, and can have different notifications for each
-     * ad.  Notifications are defined by their type and an offset in seconds.
-     * For example, 'day, 0' would send a notification at the start of 
-     * every day, and 'expire, -86400' would send a notification one day
-     * before the ad expires.
-     */
-    db_query("CREATE TABLE {ad_notify} (
-      notid INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
-      aid INT(11) UNSIGNED NOT NULL DEFAULT '0',
-      oid INT(11) UNSIGNED NOT NULL DEFAULT '0',
-
-      event VARCHAR(255) NOT NULL DEFAULT '',
-      delay INT(11) SIGNED NOT NULL DEFAULT '0',
-      queued INT(11) SIGNED NOT NULL DEFAULT '0',
-      sent INT(11) SIGNED NOT NULL DEFAULT '0',
-      counter INT(7) UNSIGNED NOT NULL DEFAULT '0',
-      locked TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
-      expire TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
-      status TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+/**
+ * Implementation of hook_schema().
+ */
+function ad_notify_schema() {
+  $schema['ad_notify'] = array(
+    'description' => 'The ad_notify table stores notifications data such as recepient, message body, event, etc.',
+    'fields' => array(
+      'notid' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'aid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'oid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'event' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => ''
+      ),
+      'delay' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'queued' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'sent' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'counter' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'locked' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'expire' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'status' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'address' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'subject' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'body' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('notid'),
+    'unique keys' => array(
+      'oid' => array('oid', 'event', 'delay'),
+    ),
+    'indexes' => array(
+      'delay' => array('delay'),
+      'event' => array('event'),
+      'oid_2' => array('oid'),
+      'queued' => array('queued'),
+      'sent' => array('sent'),
+      'status' => array('status'),
+    ),
+  );
 
-      address VARCHAR(255) NOT NULL DEFAULT '',
-      subject VARCHAR(255) NOT NULL DEFAULT '',
-      body TEXT NULL,
+  return $schema;
+}
 
-      PRIMARY KEY  (notid),
-      UNIQUE KEY  (oid, event, delay),
-      KEY  (oid),
-      KEY  (event),
-      KEY  (delay),
-      KEY  (queued),
-      KEY  (sent),
-      KEY  (status)
-    ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
-  }
-  drupal_set_message(t('The ad_notify table has been created.'));
+/**
+ * ad_notify module installation.
+ */
+function ad_notify_install() {
+  drupal_install_schema('ad_notify');
 }
 
+
 /**
  * Allow complete uninstallation of the ad_notify module.
  */
 function ad_notify_uninstall() {
   // Remove tables.
   drupal_uninstall_schema('ad_notify');
-
-  // Drop all ad_notify module tables.
-}
-
+}
\ No newline at end of file
Index: notify/ad_notify.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/notify/Attic/ad_notify.module,v
retrieving revision 1.1.2.2.2.13.2.2
diff -u -r1.1.2.2.2.13.2.2 ad_notify.module
--- notify/ad_notify.module	19 Nov 2008 17:52:34 -0000	1.1.2.2.2.13.2.2
+++ notify/ad_notify.module	27 Jan 2009 12:10:21 -0000
@@ -4,10 +4,8 @@
 /**
  * @file
  * Receive email notifications regarding ads.
- *
- * Copyright (c) 2007-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.
  */
+
 define(AD_NOTIFY_DISABLED, 0);
 define(AD_NOTIFY_ENABLED, 1);
 
@@ -28,74 +26,47 @@
  */
 function ad_notify_menu() {
   $items = array();
-
-/* TODO
-   Non menu code that was placed in hook_menu under the '!$may_cache' block
-   so that it could be run during initialization, should now be moved to hook_init.
-   Previously we called hook_init twice, once early in the bootstrap process, second
-   just after the bootstrap has finished. The first instance is now called boot
-   instead of init.
-   
-   In Drupal 6, there are now two hooks that can be used by modules to execute code
-   at the beginning of a page request. hook_boot() replaces hook_boot() in Drupal 5
-   and runs on each page request, even for cached pages. hook_boot() now only runs
-   for non-cached pages and thus can be used for code that was previously placed in
-   hook_menu() with $may_cache = FALSE:
-   
-   Dynamic menu items under a '!$may_cache' block can often be simplified
-   to remove references to arg(n) and use of '%<function-name>' to check
-   conditions. See http://drupal.org/node/103114.
-   
-   The title and description arguments should not have strings wrapped in t(),
-   because translation of these happen in a later stage in the menu system.
-*/
-  if (!$may_cache) {
-    if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'adowners' && is_numeric(arg(3))) {
-      $node = node_load(array('nid' => arg(1)));
-      $ad_user = user_load(array('uid' => arg(3)));
-      if (isset($node->adtype) && isset($ad_user->uid)) {
-        $items["node/$node->nid/adowners/$ad_user->uid/notifications"] = array(
-          'title' => '!owner\'s notifications', array('!owner' => $ad_user->name),
-          'page callback' => 'drupal_get_form',
-          'page arguments' => array('ad_notify_overview', $node->nid, $ad_user->uid),
-          'type' => MENU_LOCAL_TASK,
-          'weight' => 4,
-        );
-      }
-    }
-    if (arg(0) == 'node' && is_numeric(arg(1)) && 
-        (ad_adaccess(arg(1), 'manage own notifications') ||
-         ad_adaccess(arg(1), 'manage owners'))) {
-      global $user;
-      $node = node_load(array('nid' => arg(1)));
-      // Only display "my notifications" tab if this is an ad, and we are one
-      // of the ad's owners.
-      if (isset($node->adtype) && (db_result(db_query('SELECT oid FROM {ad_owners} WHERE aid = %d AND uid = %d', $node->nid, $user->uid)))) {
-        $items["node/$node->nid/notifications"] = array(
-          'title' => 'My notifications',
-          'page callback' => 'drupal_get_form',
-          'page arguments' => array('ad_notify_overview', $node->nid, $user->uid),
-          'type' => MENU_LOCAL_TASK,
-          'weight' => 4,
-        );
-        if (is_numeric(arg(5)) && arg(6) == 'delete') {
-          $notid = arg(5);
-          $items["node/$node->nid/adowners/$ad_user->uid/notifications/$notid/delete"] = array(
-            'title' => 'delete notification',
-            'page callback' => 'ad_notify_confirm_delete_page',
-            'page arguments' => array(ad_notify_load_notification($notid), $node->nid, $ad_user->uid),
-            'type' => MENU_CALLBACK,
-          );
-        }
-      }
-    }
-  }
+  
+  $items['node/%node/adowners/%user/notifications'] = array(
+    'title callback' => 'owner_notifications_title',
+    'title arguments' => array('!owner' => 3),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ad_notify_overview_form', 1, 3),
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, 'manage owners'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 4,
+  );
+  $items['node/%node/notifications'] = array(
+    'title' => 'My notifications',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ad_notify_overview_form', 1),
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, array('manage owners', 'manage own notifications')),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 4,
+  );
+  $items['node/%node/adowners/%user/notifications/%ad_notification/delete'] = array(
+    'title' => 'Delete notification',
+    'page callback' => 'ad_notify_confirm_delete_page',
+    'page arguments' => array(1, 3, 5),
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, array('manage owners', 'manage own notifications')),
+    'type' => MENU_CALLBACK,
+  );
 
   return $items;
 }
 
 /**
- * Drupal _cron() hook.  
+ * Menu item title callback - use the user name
+ */
+function owner_notifications_title($account) {
+  return t('!owner\'s notifications', array('!owner' => $account->name));
+}
+
+/**
+ * Implementation of hook_cron().
  * For performance reasons, all notifications are actually sent via this cron 
  * hook.
  */
@@ -167,58 +138,58 @@
   $statistics = ad_statistics($notification->aid);
   $notifications = module_invoke_all('adnotifyapi', 'register');
   $variables = array(
-    t('%owner_name') => $owner->name,
-    t('%owner_mail') => $owner->mail,
-    t('%owner_uid') => $owner->uid,
-    t('%sitename') => variable_get('site_name', 'drupal'),
-    t('%status') => $node->adstatus,
-    t('%type') => $node->adtype,
-    t('%event') => $notification->event,
-    t('%frequency') => t(strtolower($notifications[$notification->event]), array('@when' => format_interval($notification->delay))),
-    t('%redirect') => url($node->redirect, array('absolute' => TRUE)),
-    t('%aid') => $notification->aid,
-    t('%title') => $node->title,
-    t('%url') => url("node/$node->nid", array('absolute' => TRUE)),
-    t('%siteurl') => url('', array('absolute' => TRUE)),
-    t('%comments') => $node->comment_count,
-    t('%count') => $count,
-    t('%created_small') => format_date($node->created, 'small'),
-    t('%created_medium') => format_date($node->created, 'medium'),
-    t('%created_large') => format_date($node->created, 'large'),
-    t('%activated_small') => $node->activated ? format_date($node->activated, 'small') : t('never'),
-    t('%activated_medium') => $node->activated ? format_date($node->activated, 'medium') : t('never'),
-    t('%activated_large') => $node->activated ? format_date($node->activated, 'large') : t('never'),
-    t('%expired_small') => $node->expired ? format_date($node->expired, 'small') : t('never'),
-    t('%expired_medium') => $node->expired ? format_date($node->expired, 'medium') : t('never'),
-    t('%expired_large') => $node->expired ? format_date($node->expired, 'large') : t('never'),
-    t('%autoactivate_small') => $node->autoactivate ? format_date($node->autoactivate, 'small') : t('never'),
-    t('%autoactivate_medium') => $node->autoactivate ? format_date($node->autoactivate, 'medium') : t('never'),
-    t('%autoactivate_large') => $node->autoactivate ? format_date($node->autoactivate, 'large') : t('never'),
-    t('%autoexpire_small') => $node->autoexpire ? format_date($node->autoexpire, 'small') : t('never'),
-    t('%autoexpire_medium') => $node->autoexpire ? format_date($node->autoexpire, 'medium') : t('never'),
-    t('%autoexpire_large') => $node->autoexpire ? format_date($node->autoexpire, 'large') : t('never'),
-    t('%maxviews') => $node->maxviews,
-    t('%maxclicks') => $node->maxclicks,
-    t('%global_views') => $statistics['global']['views'],
-    t('%global_clicks') => $statistics['global']['clicks'],
-    t('%last_year_views') => $statistics['last_year']['views'],
-    t('%last_year_clicks') => $statistics['last_year']['clicks'],
-    t('%this_year_views') => $statistics['this_year']['views'],
-    t('%this_year_clicks') => $statistics['this_year']['clicks'],
-    t('%last_month_views') => $statistics['last_month']['views'],
-    t('%last_month_clicks') => $statistics['last_month']['clicks'],
-    t('%this_month_views') => $statistics['this_month']['views'],
-    t('%this_month_clicks') => $statistics['this_month']['clicks'],
-    t('%this_week_views') => $statistics['this_week']['views'],
-    t('%this_week_clicks') => $statistics['this_week']['clicks'],
-    t('%yesterday_views') => $statistics['yesterday']['views'],
-    t('%yesterday_clicks') => $statistics['yesterday']['clicks'],
-    t('%today_views') => $statistics['today']['views'],
-    t('%today_clicks') => $statistics['today']['clicks'],
-    t('%last_hour_views') => $statistics['last_hour']['views'],
-    t('%last_hour_clicks') => $statistics['last_hour']['clicks'],
-    t('%this_hour_views') => $statistics['this_hour']['views'],
-    t('%this_hour_clicks') => $statistics['this_hour']['clicks'],
+    '%owner_name' => $owner->name,
+    '%owner_mail' => $owner->mail,
+    '%owner_uid' => $owner->uid,
+    '%sitename' => variable_get('site_name', 'drupal'),
+    '%status' => $node->adstatus,
+    '%type' => $node->adtype,
+    '%event' => $notification->event,
+    '%frequency' => t(strtolower($notifications[$notification->event]), array('@when' => format_interval($notification->delay))),
+    '%redirect' => url($node->redirect, array('absolute' => TRUE)),
+    '%aid' => $notification->aid,
+    '%title' => $node->title,
+    '%url' => url("node/$node->nid", array('absolute' => TRUE)),
+    '%siteurl' => url('', array('absolute' => TRUE)),
+    '%comments' => $node->comment_count,
+    '%count' => $count,
+    '%created_small' => format_date($node->created, 'small'),
+    '%created_medium' => format_date($node->created, 'medium'),
+    '%created_large' => format_date($node->created, 'large'),
+    '%activated_small' => $node->activated ? format_date($node->activated, 'small') : t('never'),
+    '%activated_medium' => $node->activated ? format_date($node->activated, 'medium') : t('never'),
+    '%activated_large' => $node->activated ? format_date($node->activated, 'large') : t('never'),
+    '%expired_small' => $node->expired ? format_date($node->expired, 'small') : t('never'),
+    '%expired_medium' => $node->expired ? format_date($node->expired, 'medium') : t('never'),
+    '%expired_large' => $node->expired ? format_date($node->expired, 'large') : t('never'),
+    '%autoactivate_small' => $node->autoactivate ? format_date($node->autoactivate, 'small') : t('never'),
+    '%autoactivate_medium' => $node->autoactivate ? format_date($node->autoactivate, 'medium') : t('never'),
+    '%autoactivate_large' => $node->autoactivate ? format_date($node->autoactivate, 'large') : t('never'),
+    '%autoexpire_small' => $node->autoexpire ? format_date($node->autoexpire, 'small') : t('never'),
+    '%autoexpire_medium' => $node->autoexpire ? format_date($node->autoexpire, 'medium') : t('never'),
+    '%autoexpire_large' => $node->autoexpire ? format_date($node->autoexpire, 'large') : t('never'),
+    '%maxviews' => $node->maxviews,
+    '%maxclicks' => $node->maxclicks,
+    '%global_views' => $statistics['global']['views'],
+    '%global_clicks' => $statistics['global']['clicks'],
+    '%last_year_views' => $statistics['last_year']['views'],
+    '%last_year_clicks' => $statistics['last_year']['clicks'],
+    '%this_year_views' => $statistics['this_year']['views'],
+    '%this_year_clicks' => $statistics['this_year']['clicks'],
+    '%last_month_views' => $statistics['last_month']['views'],
+    '%last_month_clicks' => $statistics['last_month']['clicks'],
+    '%this_month_views' => $statistics['this_month']['views'],
+    '%this_month_clicks' => $statistics['this_month']['clicks'],
+    '%this_week_views' => $statistics['this_week']['views'],
+    '%this_week_clicks' => $statistics['this_week']['clicks'],
+    '%yesterday_views' => $statistics['yesterday']['views'],
+    '%yesterday_clicks' => $statistics['yesterday']['clicks'],
+    '%today_views' => $statistics['today']['views'],
+    '%today_clicks' => $statistics['today']['clicks'],
+    '%last_hour_views' => $statistics['last_hour']['views'],
+    '%last_hour_clicks' => $statistics['last_hour']['clicks'],
+    '%this_hour_views' => $statistics['this_hour']['views'],
+    '%this_hour_clicks' => $statistics['this_hour']['clicks'],
   );
   // TODO: Add hook to allow other modules to define variables.
 
@@ -233,7 +204,7 @@
 }
 
 /**
- * Adapi _hook implementation.
+ * Implementation of hook_adapi().
  */
 function ad_notify_adapi($op, &$node) {
   $output = NULL;
@@ -247,6 +218,9 @@
   }
 }
 
+/**
+ * Implementation of hook_adnotifyapi().
+ */
 function ad_notify_adnotifyapi($op, $arg1 = NULL, $arg2 = NULL) {
   switch ($op) {
     case 'register':
@@ -258,9 +232,9 @@
 }
 
 /**
- * Function stub.
+ * Notification overview form.
  */
-function ad_notify_overview($aid, $uid, $notid = 0) {
+function ad_notify_overview_form($form_state, $node, $owner = NULL, $notid = 0) {
   global $user;
   if (arg(2) == 'notifications') {
     drupal_set_title('My notifications');
@@ -268,16 +242,19 @@
   else {
     drupal_set_title('Notifications');
   }
+  
+  if (!isset($owner)) {
+    $owner = $user;
+  }
 
-  $oid = db_result(db_query('SELECT oid FROM {ad_owners} WHERE aid = %d AND uid = %d', $aid, $uid));
+  $oid = db_result(db_query('SELECT oid FROM {ad_owners} WHERE aid = %d AND uid = %d', $node->nid, $owner->uid));
   $notifications = module_invoke_all('adnotifyapi', 'register');
-  $owner = user_load(array('uid' => $uid));
 
   $header = array(
-    array('data' => t('last sent'), 'field' => 'sent', 'sort' => 'desc'),
-    array('data' => t('notification'), 'field' => 'event'),
-    array('data' => t('status'), 'field' => 'status'),
-    array('data' => t('action'))
+    array('data' => t('Last sent'), 'field' => 'sent', 'sort' => 'desc'),
+    array('data' => t('Notification'), 'field' => 'event'),
+    array('data' => t('Status'), 'field' => 'status'),
+    array('data' => t('Action'))
   );
   $sql = "SELECT notid, event, delay, sent, address, status FROM {ad_notify} WHERE oid = $oid";
   $sql .= tablesort_sql($header);
@@ -289,7 +266,7 @@
     $row[] = $notify->sent ? format_interval(time() - $notify->sent). t(' ago') : t('Never');
     $row[] = t($notifications[$notify->event], array('@when' => format_interval($notify->delay)));
     $row[] = $notify->status == AD_NOTIFY_ENABLED ? t('enabled') : t('disabled');
-    $row[] = l(t('edit'), "node/$aid/adowners/$uid/notifications/$notify->notid/edit") .' '. l(t('delete'), "node/$aid/adowners/$uid/notifications/$notify->notid/delete");
+    $row[] = l(t('edit'), 'node/'. $node->nid .'/adowners/'. $owner->uid .'/notifications/' .$notify->notid. '/edit') .' '. l(t('delete'), 'node/'. $node->nid .'/adowners/'. $owner->uid .'/notifications/'. $notify->notid .'/delete');
     $rows[] = $row;
   }
   $output = theme('table', $header, $rows);
@@ -298,7 +275,7 @@
   $form = array();
 
   if ($notid) {
-    $notification = ad_notify_load_notification($notid);
+    $notification = ad_notification_load($notid);
   }
 
   $help = '<p>'. t('You can configure one or more notifications for your advertisement using the drop down menus below.  For example, to receive a weekly notification with information about how often your advertisement was viewed and clicked, select the <em>email every @when as long as the ad is active</em> event, and <em>1 week</em> for when.  Or, to receive a reminder that your advertisement will expire in 24 hours select the <em>email @when before the advertisement will expire</em>, and <em>1 day</em> for when.') .'</p>';
@@ -306,7 +283,7 @@
   $form['create'] = array(
     '#type' => 'fieldset',
     '#description' => $help,
-    '#title' => $notid ? t('edit notification') : t('create new notification'),
+    '#title' => $notid ? t('Edit notification') : t('Create new notification'),
     '#collapsible' => TRUE,
     '#collapsed' => ($rows == array() || $notid) ? FALSE : TRUE,
   );
@@ -353,7 +330,7 @@
   if ($notid) {
     $form['create']['mail'] = array(
       '#type' => 'fieldset',
-      '#title' => t('message'),
+      '#title' => t('Message'),
       '#collapsible' => TRUE,
       '#collapsed' => TRUE,
     );
@@ -388,30 +365,30 @@
       '#required' => TRUE,
       '#default_value' => $notification->body,
       '#description' => t('Enter the body of your notification email.  The following variables can be used in your message and will be automatically replaced before the email is sent:') .'<ul>'
-.'<li>'. t('%sitename: the name of this website.', array('%sitename' => '%sitename')) 
-.'<li>'. t('%owner_name: the username of the ad owner.', array('%owner_name' => '%owner_name')) 
-.'<li>'. t('%owner_mail: the email address of the ad owner.', array('%owner_mail' => '%owner_mail')) 
-.'<li>'. t('%owner_uid: the user ID of the ad owner.', array('%owner_uid' => '%owner_uid')) 
-.'<li>'. t('%event: the type of event that has triggered this notification.', array('%event' => '%event'))
-.'<li>'. t('%count: the number of times the event happened.', array('%count' => '%count')) 
-.'<li>'. t('%frequency: a complete sentence describing the frequency this notification will be sent.', array('%frequency' => '%frequency')) 
-.'<li>'. t('%type: the type of ad.', array('%type' => '%type')) 
-.'<li>'. t('%status: the status of the ad.', array('%status' => '%status')) 
-.'<li>'. t('%url: the url of the advertisement.', array('%url' => '%url')) 
-.'<li>'. t('%siteurl: the url of the website.', array('%siteurl' => '%siteurl')) 
-.'<li>'. t('%redirect: the redirection url of the advertisement.', array('%redirect' => '%redirect')) 
-.'<li>'. t('%title: the title of the advertisement.', array('%title' => '%title')) 
-.'<li>'. t('%aid: the ID of the advertisement.', array('%aid' => '%aid')) 
-.'<li>'. t('%comments: the number of comments attached to the advertisement.', array('%comments' => '%comments')) 
-.'<li>'. t('%created_small, %created_medium, %created_large: various formats of when the advertisement was created.', array('%created_small' => '%created_small', '%created_medium' => 'created_medium', '%created_large' => 'created_large')) 
-.'<li>'. t('%activated_small, %activated_medium, %activated_large: various formats of when the advertisement was activated.', array('%activated_small' => '%activated_small', '%activated_medium' => 'activated_medium', '%activated_large' => 'activated_large')) 
-.'<li>'. t('%expired_small, %expired_medium, %expired_large: various formats of when the advertisement was expired.', array('%expired_small' => '%expired_small', '%expired_medium' => 'expired_medium', '%expired_large' => 'expired_large')) 
-.'<li>'. t('%autoactivate_small, %autoactivate_medium, %autoactivate_large: various formats of when the advertisement was automatically activated.', array('%autoactivate_small' => '%autoactivate_small', '%autoactivate_medium' => 'autoactivate_medium', '%autoactivate_large' => 'autoactivate_large')) 
-.'<li>'. t('%autoexpire_small, %autoexpire_medium, %autoexpire_large: various formats of when the advertisement was automatically expired.', array('%autoexpire_small' => '%autoexpire_small', '%autoexpire_medium' => 'autoexpire_medium', '%autoexpire_large' => 'autoexpire_large')) 
-.'<li>'. t('%maxviews: the maximum number of times this advertisement is allowed to be viewed.', array('%maxviews' => '%maxviews')) 
-.'<li>'. t('%maxclicks: the maximum number of times this advertisement is allowed to be clicked.', array('%maxclicks' => '%maxclicks')) 
-.'<li>'. t('%global_views, %global_clicks, %last_year_views, %last_year_clicks, %this_year_views, %this_year_clicks, %last_month_views, %last_month_clicks, %this_month_views, %this_month_clicks, %this_week_views, %this_week_clicks, %yesterday_views, %yesterday_clicks, %today_views, %today_clicks, %last_hour_views, %last_hour_clicks, %this_hour_views, %this_hour_clicks: various advertisement statistics', array('%global_views' => '%global_views', '%global_clicks' => '%global_views', '%last_year_views' => '%last_year_views', '%last_year_clicks' => '%last_year_clicks', '%this_year_views' => '%this_year_views', '%this_year_clicks' => '%this_year_clicks', '%this_hour_views' => '%this_hour_views', '%this_hour_clicks' => '%this_hour_clicks'))
-.'</ul>',
+          .'<li>'. t('%sitename: the name of this website.') 
+          .'<li>'. t('%owner_name: the username of the ad owner.') 
+          .'<li>'. t('%owner_mail: the email address of the ad owner.') 
+          .'<li>'. t('%owner_uid: the user ID of the ad owner.') 
+          .'<li>'. t('%event: the type of event that has triggered this notification.')
+          .'<li>'. t('%count: the number of times the event happened.') 
+          .'<li>'. t('%frequency: a complete sentence describing the frequency this notification will be sent.') 
+          .'<li>'. t('%type: the type of ad.') 
+          .'<li>'. t('%status: the status of the ad.') 
+          .'<li>'. t('%url: the url of the advertisement.') 
+          .'<li>'. t('%siteurl: the url of the website.') 
+          .'<li>'. t('%redirect: the redirection url of the advertisement.') 
+          .'<li>'. t('%title: the title of the advertisement.') 
+          .'<li>'. t('%aid: the ID of the advertisement.') 
+          .'<li>'. t('%comments: the number of comments attached to the advertisement.') 
+          .'<li>'. t('%created_small, %created_medium, %created_large: various formats of when the advertisement was created.') 
+          .'<li>'. t('%activated_small, %activated_medium, %activated_large: various formats of when the advertisement was activated.') 
+          .'<li>'. t('%expired_small, %expired_medium, %expired_large: various formats of when the advertisement was expired.') 
+          .'<li>'. t('%autoactivate_small, %autoactivate_medium, %autoactivate_large: various formats of when the advertisement was automatically activated.') 
+          .'<li>'. t('%autoexpire_small, %autoexpire_medium, %autoexpire_large: various formats of when the advertisement was automatically expired.') 
+          .'<li>'. t('%maxviews: the maximum number of times this advertisement is allowed to be viewed.') 
+          .'<li>'. t('%maxclicks: the maximum number of times this advertisement is allowed to be clicked.') 
+          .'<li>'. t('%global_views, %global_clicks, %last_year_views, %last_year_clicks, %this_year_views, %this_year_clicks, %last_month_views, %last_month_clicks, %this_month_views, %this_month_clicks, %this_week_views, %this_week_clicks, %yesterday_views, %yesterday_clicks, %today_views, %today_clicks, %last_hour_views, %last_hour_clicks, %this_hour_views, %this_hour_clicks: various advertisement statistics')
+          .'</ul>',
     );
   }
 
@@ -422,12 +399,12 @@
 
   $form['create']['aid'] = array(
     '#type' => 'hidden',
-    '#value' => $aid,
+    '#value' => $node->nid,
   );
 
   $form['create']['uid'] = array(
     '#type' => 'hidden',
-    '#value' => $uid,
+    '#value' => $owner->uid,
   );
 
   if ($notid) {
@@ -435,24 +412,30 @@
       '#type' => 'hidden',
       '#value' => $notid,
     );
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+    );
   }
-
-  $form['create']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => $notid ? t('edit notification') : t('create notification'),
-  );
-
-  if ($rows != array()) {
-    $form['notifications'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('notifications'),
-      '#collapsible' => TRUE,
+  else {
+    $form['create']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Create notification'),
     );
+  }
 
-    $form['notifications']['current'] = array(
-      '#type' => 'markup',
-      '#value' => $output,
-    );
+  if ($rows != array()) {
+    if (!$notid) {
+      $form['notifications'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Notifications'),
+        '#collapsible' => TRUE,
+      );
+      $form['notifications']['current'] = array(
+        '#type' => 'markup',
+        '#value' => $output,
+      );
+    }
   }
   else {
     $form['notifications'] = array(
@@ -465,23 +448,77 @@
 }
 
 /**
+ * Validate ad notifications before saving to database.
+ */
+function ad_notify_overview_form_validate($form, &$form_state) {
+  $redirect = FALSE;
+  if ($form_state['values']['event'] == 'regular' && $form_state['values']['delay'] < 3600) {
+    drupal_set_message(t('You are not allowed to schedule a regular notification more frequently than once an hour.'), 'error');
+    $redirect = TRUE;
+  }
+  else if (!$form_state['values']['notid']) {
+    if (db_result(db_query("SELECT notid FROM {ad_notify} WHERE oid = %d AND event = '%s' AND delay = %d", $form_state['values']['oid'], $form_state['values']['event'], $form_state['values']['delay']))) {
+      drupal_set_message(t('You have already scheduled that notification.'), 'error');
+      $redirect = TRUE;
+    }
+  }
+  else if ($form_state['values']['locked'] && !ad_adaccess($form_state['values']['aid'], 'manage owners')) {
+    $redirect = TRUE;
+    drupal_set_message(t('This notification is locked, you will need to contact the site administrator to edit this notification for you.'), 'error');
+  }
+
+  if ($redirect) {
+    if (arg(2) == 'adowners' && arg(4) == 'notifications') {
+      drupal_goto('node/'. $form_state['values']['aid'] .'/adowners/'. $form_state['values']['uid'] .'/notifications');
+    }
+    else {
+      drupal_goto('node/'. $form_state['values']['aid'] .'/notifications');
+    }
+  }
+}
+
+/**
+ * Save notifications to database.
+ */
+function ad_notify_overview_form_submit($form, &$form_state) {
+  if ($form_state['values']['notid']) {
+    db_query("UPDATE {ad_notify} SET aid = %d, oid = %d, event = '%s', delay = %d, expire = %d, locked = %d, status = %d, address = '%s', subject = '%s', body = '%s' WHERE notid = %d", $form_state['values']['aid'], $form_state['values']['oid'], $form_state['values']['event'], $form_state['values']['delay'], $form_state['values']['expire'], $form_state['values']['locked'], AD_NOTIFY_ENABLED, $form_state['values']['address'], $form_state['values']['subject'], $form_state['values']['body'], $form_state['values']['notid']);
+    drupal_set_message('Notification updated.');
+  }
+  else {
+    // Retrieve the default mail subject and body.
+    $mail = module_invoke_all('adnotifyapi', 'mail_text', $form_state['values']['event']);
+    if ($mail == array()) {
+      // Default message text.
+      $mail = array(
+        'subject' => t('[%sitename ad] %event notification'),
+        'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification about your advertisement \"%title\" that is being displayed on the %sitename website.\n\n  Your advertisement has been viewed %today_views times and clicked %today_clicks times today.  It was viewed %yesterday_views times and clicked %yesterday_clicks times yesterday.  It has been viewed %global_views times and clicked %global_clicks times since it was activated on %activated_large.\n\n  You will receive this %frequency  You can view additional statistics about this advertisement or update this notification at the following url:\n    %url\n\nRegards,\n The %sitename Team\n\n-\n%siteurl"),
+      );
+    }
+    db_query("INSERT INTO {ad_notify} (aid, oid, event, delay, expire, locked, status, address, subject, body) VALUES(%d, %d, '%s', %d, %d, %d, %d, '%s', '%s', '%s')", $form_state['values']['aid'], $form_state['values']['oid'], $form_state['values']['event'], $form_state['values']['delay'], $form_state['values']['expire'], $form_state['values']['locked'], AD_NOTIFY_ENABLED, $form_state['values']['address'], $mail['subject'], $mail['body']);
+    drupal_set_message('Notification created.');
+  }
+  $form_state['redirect'] = 'node/'. $form_state['values']['aid'] .'/adowners/'. $form_state['values']['uid'] .'/notifications';
+}
+
+/**
  * Load a specified notification from the database, return as an object.
  */
-function ad_notify_load_notification($notid) {
+function ad_notification_load($notid) {
   return db_fetch_object(db_query('SELECT * FROM {ad_notify} WHERE notid = %d', $notid));
 }
 
 /**
  * Display confirm form.
  */
-function ad_notify_confirm_delete_page($notification, $aid, $uid) {
-  return drupal_get_form('ad_notify_confirm_delete', $notification, $aid, $uid);
+function ad_notify_confirm_delete_page($node, $owner, $notification) {
+  return drupal_get_form('ad_notify_confirm_delete', $node, $owner, $notification);
 }
 
 /**
  * Confirm deletion of a specified notification from the database.
  */
-function ad_notify_confirm_delete(&$form_state, $notification, $aid, $uid) {
+function ad_notify_confirm_delete(&$form_state, $node, $owner, $notification) {
   $form = array();
 
   $form['oid'] = array(
@@ -491,12 +528,12 @@
 
   $form['aid'] = array(
     '#type' => 'hidden',
-    '#value' => $aid,
+    '#value' => $node->nid,
   );
 
   $form['uid'] = array(
     '#type' => 'hidden',
-    '#value' => $uid,
+    '#value' => $owner->uid,
   );
 
   $form['notid'] = array(
@@ -523,11 +560,12 @@
 
   $form = confirm_form(
     $form,
-    t('Are you sure you want to delete this notification?'),
-    "node/$aid/adowners/$uid/notifications",
-    t('This action cannot be undone.'),
-    t('Delete'),
-    t('Cancel'));
+    'Are you sure you want to delete this notification?',
+    'node/'. $node->nid .'/adowners/'. $owner->uid .'/notifications',
+    'This action cannot be undone.',
+    'Delete',
+    'Cancel'
+  );
   return $form;
 }
 
@@ -553,66 +591,16 @@
 function ad_notify_confirm_delete_submit($form, &$form_state) {
   db_query('DELETE FROM {ad_notify} WHERE notid = %d', $form_state['values']['notid']);
   drupal_set_message('Notification deleted.');
-  drupal_goto('node/'. $form_state['values']['aid'] .'/adowners/'. $form_state['values']['uid'] .'/notifications');
+  $form_state['redirect'] = 'node/'. $form_state['values']['aid'] .'/adowners/'. $form_state['values']['uid'] .'/notifications';
 }
 
 /**
- * Validate ad notifications before saving to database.
- */
-function ad_notify_overview_validate($form, &$form_state) {
-  $redirect = FALSE;
-  if ($form_state['values']['event'] == 'regular' && $form_state['values']['delay'] < 3600) {
-    drupal_set_message(t('You are not allowed to schedule a regular notification more frequently than once an hour.'), 'error');
-    $redirect = TRUE;
-  }
-  else if (!$form_state['values']['notid']) {
-    if (db_result(db_query("SELECT notid FROM {ad_notify} WHERE oid = %d AND event = '%s' AND delay = %d", $form_state['values']['oid'], $form_state['values']['event'], $form_state['values']['delay']))) {
-      drupal_set_message(t('You have already scheduled that notification.'), 'error');
-      $redirect = TRUE;
-    }
-  }
-  else if ($form_state['values']['locked'] && !ad_adaccess($form_state['values']['aid'], 'manage owners')) {
-    $redirect = TRUE;
-    drupal_set_message(t('This notification is locked, you will need to contact the site administrator to edit this notification for you.'), 'error');
-  }
-
-  if ($redirect) {
-    if (arg(2) == 'adowners' && arg(4) == 'notifications') {
-      drupal_goto('node/'. $form_state['values']['aid'] .'/adowners/'. $form_state['values']['uid'] .'/notifications');
-    }
-    else {
-      drupal_goto('node/'. $form_state['values']['aid'] .'/notifications');
-    }
-  }
-}
-
-function ad_notify_overview_submit($form, &$form_state) {
-  if ($form_state['values']['notid']) {
-    db_query("UPDATE {ad_notify} SET aid = %d, oid = %d, event = '%s', delay = %d, expire = %d, locked = %d, status = %d, address = '%s', subject = '%s', body = '%s' WHERE notid = %d", $form_state['values']['aid'], $form_state['values']['oid'], $form_state['values']['event'], $form_state['values']['delay'], $form_state['values']['expire'], $form_state['values']['locked'], AD_NOTIFY_ENABLED, $form_state['values']['address'], $form_state['values']['subject'], $form_state['values']['body'], $form_state['values']['notid']);
-    drupal_set_message('Notification updated.');
-  }
-  else {
-    // Retrieve the default mail subject and body.
-    $mail = module_invoke_all('adnotifyapi', 'mail_text', $form_state['values']['event']);
-    if ($mail == array()) {
-      // Default message text.
-      $mail = array(
-        'subject' => t('[%sitename ad] %event notification'),
-        'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification about your advertisement \"%title\" that is being displayed on the %sitename website.\n\n  Your advertisement has been viewed %today_views times and clicked %today_clicks times today.  It was viewed %yesterday_views times and clicked %yesterday_clicks times yesterday.  It has been viewed %global_views times and clicked %global_clicks times since it was activated on %activated_large.\n\n  You will receive this %frequency  You can view additional statistics about this advertisement or update this notification at the following url:\n    %url\n\nRegards,\n The %sitename Team\n\n-\n%siteurl"),
-      );
-    }
-    db_query("INSERT INTO {ad_notify} (aid, oid, event, delay, expire, locked, status, address, subject, body) VALUES(%d, %d, '%s', %d, %d, %d, %d, '%s', '%s', '%s')", $form_state['values']['aid'], $form_state['values']['oid'], $form_state['values']['event'], $form_state['values']['delay'], $form_state['values']['expire'], $form_state['values']['locked'], AD_NOTIFY_ENABLED, $form_state['values']['address'], $mail['subject'], $mail['body']);
-    drupal_set_message('Notification created.');
-  }
-}
-
-/**
- * Adowners _hook().
+ * Implementation of hook_adowners().
  */
 function ad_notify_adowners($op, $arg1 = NULL, $arg2 = NULL) {
   switch ($op) {
     case 'overview':
-      return l(t('notifications'), "node/$arg1/adowners/$arg2/notifications");
+      return l(t('notifications'), 'node/'. $arg1 .'/adowners/'. $arg2 .'/notifications');
 
     case 'delete':
       if ($arg1) {
@@ -620,5 +608,4 @@
       }
       break;
   }
-}
-
+}
\ No newline at end of file
Index: remote/ad_remote.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/remote/Attic/ad_remote.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_remote.info
--- remote/ad_remote.info	11 Aug 2008 21:15:27 -0000	1.1.2.1.2.1
+++ remote/ad_remote.info	20 Nov 2008 00:00:38 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = ad
 description = Generates cut-and-paste source snippets allowing ads to be easily displayed on remote websites.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: remote/ad_remote.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/remote/Attic/ad_remote.module,v
retrieving revision 1.1.4.5.2.1
diff -u -r1.1.4.5.2.1 ad_remote.module
--- remote/ad_remote.module	11 Aug 2008 21:15:27 -0000	1.1.4.5.2.1
+++ remote/ad_remote.module	27 Jan 2009 12:00:42 -0000
@@ -4,82 +4,100 @@
 /**
 * @file
  * Enhances the ad module to providing cut-and-paste source snippets allowing 
- * ads to be easily displayed on remote websites.  This module is a proof of 
- * concept.
- *
- * Copyright (c) 2007-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.
+ * ads to be easily displayed on remote websites.
  */
 
 /**
- * Drupal _perm hook.
+ * Implementation of hook_perm().
  */
 function ad_remote_perm() {
   return array('host remote advertisements');
 }
 
 /**
- * Drupal _menu hook.
+ * Implementation of hook_menu().
  */
 function ad_remote_menu() {
-  global $user;
   $items = array();
 
-/* TODO
-   Non menu code that was placed in hook_menu under the '!$may_cache' block
-   so that it could be run during initialization, should now be moved to hook_init.
-   Previously we called hook_init twice, once early in the bootstrap process, second
-   just after the bootstrap has finished. The first instance is now called boot
-   instead of init.
-   
-   In Drupal 6, there are now two hooks that can be used by modules to execute code
-   at the beginning of a page request. hook_boot() replaces hook_boot() in Drupal 5
-   and runs on each page request, even for cached pages. hook_boot() now only runs
-   for non-cached pages and thus can be used for code that was previously placed in
-   hook_menu() with $may_cache = FALSE:
-   
-   Dynamic menu items under a '!$may_cache' block can often be simplified
-   to remove references to arg(n) and use of '%<function-name>' to check
-   conditions. See http://drupal.org/node/103114.
-   
-   The title and description arguments should not have strings wrapped in t(),
-   because translation of these happen in a later stage in the menu system.
-*/
-  if ($may_cache) {
-    $items["ad_remote"] = array(
-      'title' => 'Remote ads',
-      'page callback' => 'drupal_get_form',
-      'page arguments' => array('ad_remote_form'),
-      'access arguments' => array('host remote advertisements', $user));
-  }
-
+  $items['admin/content/ad/ad_remote'] = array(
+    'title' => 'Remote ads',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ad_remote_form'),
+    'access arguments' => array('host remote advertisements'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 1,
+  );
   return $items;
 }
 
 /**
  * A simple page providing source snippets for displaying ads on remote 
- * websites.  
+ * websites. When form is being submitted, it rebuilds with needed code snippet.
  */
-function ad_remote_form() {
+function ad_remote_form($form_state) {
   global $user;
 
-  $form = array();
-
-  $form['overview'] = array(
-    '#type' => 'markup',
-    '#value' => t('Use the following options to build a source snippet for displaying ads on your website.'),
-    '#weight' => -16,
+  $form['settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Settings'),
+    '#description' => t('Use the following options to build a source snippet for displaying ads on your website.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => isset($form_state['values']['group']),
+    '#weight' => -1,
   );
+  
+  $form['settings']['group'] = taxonomy_form(_ad_get_vid(), 0, t('Select one or more groups to display ads from.'));
+  $form['settings']['group']['#default_value'] = $form_state['values']['group'];
 
-  $form['group'] = taxonomy_form(_ad_get_vid(), 0, t('Select one or more groups to display ads from.'));
-
-  $form['quantity'] = array(
+  if (isset($form_state['values']['quantity'])) {
+    // Sanity chacke, be sure quantity is an integer.
+    $quantity = (int)$form_state['values']['quantity'];
+  }
+  if (!$quantity) {
+    // Must display at least one advertisement.
+    $quantity = 1;
+  }
+  
+  $form['settings']['quantity'] = array(
     '#type' => 'select',
     '#title' => t('Quantity'),
     '#options' => drupal_map_assoc(array(1,2,3,4,5,6,7,8,9,10,15,20,25,50)),
-    '#default_value' => isset($_POST['quantity']) ? $_POST['quantity'] : 1,
+    '#default_value' => $quantity,
     '#description' => t('Select the maximum number of unique ads that should be displayed together.'),
   );
+  
+  if (isset($form_state['values']['group'])) {
+    $form['code'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Code snippet'),
+      '#description' => t('Insert the following source snippet into your web page to display ads hosted on this web site.  Include the entire snippet, and do not modify it in any way.'),
+    );
+
+    $hostid = ad_host_id_create($user->uid);
+    $group = NULL;
+    if (is_array($form_state['values']['group']) && !empty($form_state['values']['group'])) {
+      if ($form_state['values']['group'][0] == 0) {
+        unset($form_state['values']['group'][0]);
+      }
+      $group = implode(',', $form_state['values']['group']);
+      // Sanity check, be sure group is only numbers and commas.
+      $group = preg_replace('/[^0-9,]/', '', $group);
+    }
+    if (!$group) {
+      $group = 0;
+    }
+
+    $output .= '<--'. t('start') .'-->'. ad($group, $quantity, array('raw' => 1, 'hostid' => $hostid)) .'<!--'. t('end') .'-->';
+    $form['code']['snippet'] = array(
+      '#type' => 'textarea',
+      '#value' => $output,
+      '#attributes' => array(
+        'onclick' => 'this.select();',
+        'onfocus' => 'this.select();',
+      ),
+    );
+  }
 
   $form['submit'] = array(
     '#type' => 'submit',
@@ -90,44 +108,17 @@
 }
 
 /**
- * Prepare quantity and group.
+ * Form validator.
  */
-function ad_remote_form_submit($form, &$form_state) {
-  global $user;
-
-  if (isset($form_state['values']['quantity'])) {
-    // Sanity chacke, be sure quantity is an integer.
-    $quantity = (int)$form_state['values']['quantity'];
-  }
-  if (!$quantity) {
-    // Must display at least one advertisement.
-    $quantity = 1;
-  }
-  $group = NULL;
-  if (is_array($form_state['values']['group']) && !empty($form_state['values']['group'])) {
-    if ($form_state['values']['group'][0] == 0) {
-      unset($form_state['values']['group'][0]);
-    }
-    $group = implode(',', $form_state['values']['group']);
-    // Sanity check, be sure group is only numbers and commas.
-    $group = preg_replace('/[^0-9,]/', '', $group);
+function ad_remote_form_validate($form, &$form_state) {
+  if (empty($form_state['values']['group'])) {
+    form_set_error('group', t('At least one group should be selected'));
   }
-  if (!$group) {
-    $group = 0;
-  }
-  $output = '<div class="ad_remote">';
-
-  // Instructions.
-  $output .= '<div class="instructions">';
-  $output .= t('Insert the following source snippet into your web page to display ads hosted on this web site.  Include the entire snippet, and do not modify it in any way.');
-  $output .= '</div>';
- 
-
-  // Output the actual source snippet.
-  $output .= '<div class="snippet">';
-  $hostid = ad_host_id_create($user->uid);
-  $output .= '<br />&lt;!--'. t('start') .'--&gt;<br />'. htmlentities(ad($group, $quantity, array('raw' => 1, 'hostid' => $hostid))) .'<br />&lt;!--'. t('end') .'--&gt;';
-  $output .= '</div></div>';
-  print theme('page', $output);
+}
+/**
+ * Tell the form to rebuild.
+ */
+function ad_remote_form_submit($form, &$form_state) {
+  $form_state['rebuild'] = TRUE;
 }
 
Index: report/ad_report.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/report/Attic/ad_report.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 ad_report.info
--- report/ad_report.info	11 Aug 2008 21:15:27 -0000	1.1.2.1.2.1
+++ report/ad_report.info	20 Nov 2008 00:00:38 -0000
@@ -2,4 +2,10 @@
 name = Report
 package = Ad
 dependencies[] = ad
-description = Provides comprehensive charts and reports about advertising statistics.core = 6.x
\ No newline at end of file
+description = Provides comprehensive charts and reports about advertising statistics.core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: report/ad_report.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/report/Attic/ad_report.module,v
retrieving revision 1.1.2.3.2.7.2.2
diff -u -r1.1.2.3.2.7.2.2 ad_report.module
--- report/ad_report.module	19 Nov 2008 19:29:58 -0000	1.1.2.3.2.7.2.2
+++ report/ad_report.module	26 Jan 2009 13:51:05 -0000
@@ -4,9 +4,6 @@
 /**
  * @file
  * Provides comprehensive charts and reports about advertising statistics.
- *
- * Copyright (c) 2007-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.
  */
 
 /**
@@ -14,66 +11,66 @@
  */
 function ad_report_menu() {
   $items = array();
-     $items["node/%node/report"] = array(
-          'title' => 'Reports',
-          'page callback' => 'ad_report_bargraph',
-          'page arguments' => array(1),
-          'type' => MENU_LOCAL_TASK,
-          'access callback' => 'ad_report_access',
-          'access arguments' => array(1),
-          );
-     
-      $items["node/%node/report/monthly"] = array(
-          'title' => 'Monthly Reports',
-          'page callback' => 'ad_report_bargraph',
-          'page arguments' => array(1,3),
-          'type' => MENU_LOCAL_TASK,
-          'access callback' => 'ad_report_access',
-          'access arguments' => array(1),
-          );
-
-     $items["node/%node/report/weekly"] = array(
-          'title' => 'Weekly Reports',
-          'page callback' => 'ad_report_bargraph',
-          'page arguments' => array(1,3),
-          'type' => MENU_LOCAL_TASK,
-          'access callback' => 'ad_report_access',
-          'access arguments' => array(1),
-          );
-     $items["node/%node/report/daily"] = array(
-          'title' => 'Daily Reports',
-          'page callback' => 'ad_report_bargraph',
-          'page arguments' => array(1,3),
-          'type' => MENU_DEFAULT_LOCAL_TASK,
-          'access callback' => 'ad_report_access',
-          'access arguments' => array(1),
-          ); 
-     $items["node/%node/report/hourly"] = array(
-          'title' => 'Hourly Reports',
-          'page callback' => 'ad_report_bargraph',
-          'page arguments' => array(1,3),
-          'type' => MENU_LOCAL_TASK,
-          'access callback' => 'ad_report_access',
-          'access arguments' => array(1),
-          );
-     $items["ad_report/%node/bargraph"] = array(
-          'title' => 'Bar graph',
-          'page callback' => 'ad_report_generate_bargraph',
-          'page arguments' => array(1),
-          'type' => MENU_CALLBACK,
-          'access callback' => 'ad_report_access',
-          'access arguments' => array(1),
-        );
-        
-      return $items;
+
+  $items['node/%node/report'] = array(
+    'title' => 'Reports',
+    'page callback' => 'ad_report_bargraph',
+    'page arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+    'access callback' => 'ad_report_access',
+    'access arguments' => array(1),
+  );
+  $items['node/%node/report/monthly'] = array(
+    'title' => 'Monthly Reports',
+    'page callback' => 'ad_report_bargraph',
+    'page arguments' => array(1,3),
+    'type' => MENU_LOCAL_TASK,
+    'access callback' => 'ad_report_access',
+    'access arguments' => array(1),
+  );
+  $items['node/%node/report/weekly'] = array(
+    'title' => 'Weekly Reports',
+    'page callback' => 'ad_report_bargraph',
+    'page arguments' => array(1,3),
+    'type' => MENU_LOCAL_TASK,
+    'access callback' => 'ad_report_access',
+    'access arguments' => array(1),
+  );
+  $items['node/%node/report/daily'] = array(
+    'title' => 'Daily Reports',
+    'page callback' => 'ad_report_bargraph',
+    'page arguments' => array(1,3),
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'access callback' => 'ad_report_access',
+    'access arguments' => array(1),
+  ); 
+  $items['node/%node/report/hourly'] = array(
+    'title' => 'Hourly Reports',
+    'page callback' => 'ad_report_bargraph',
+    'page arguments' => array(1,3),
+    'type' => MENU_LOCAL_TASK,
+    'access callback' => 'ad_report_access',
+    'access arguments' => array(1),
+  );
+  $items['ad_report/%node/bargraph'] = array(
+    'title' => 'Bar graph',
+    'page callback' => 'ad_report_generate_bargraph',
+    'page arguments' => array(1),
+    'type' => MENU_CALLBACK,
+    'access callback' => 'ad_report_access',
+    'access arguments' => array(1),
+  );
+
+  return $items;
 }
 /**
-* Implementation of access callback
+* Implementation of access callback.
 * 
 * @param mixed $node
+*  Ad object.
 */
 function ad_report_access($node){
-    return ($node->type == 'ad') && ad_adaccess($node->nid, 'access statistics');
+    return ($node->type == 'ad') && ad_adaccess($node, 'access statistics');
 }
 /**
  * Page to display ad with bargraph.
@@ -81,16 +78,16 @@
 function ad_report_bargraph($node, $granularity = 'daily', $type = 'node') {
   switch ($granularity) {
     case 'hourly':
-      drupal_set_title(t('past twelve hours'));
+      drupal_set_title(t('Past twelve hours'));
       break;
     case 'daily':
-      drupal_set_title(t('past twelve days'));
+      drupal_set_title(t('Past twelve days'));
       break;
     case 'weekly':
-      drupal_set_title(t('past twelve weeks'));
+      drupal_set_title(t('Past twelve weeks'));
       break;
     case 'monthly':
-      drupal_set_title(t('past twelve months'));
+      drupal_set_title(t('Past twelve months'));
       break;
   }
 
Index: statistics/click_filter/click_filter.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/statistics/click_filter/Attic/click_filter.info,v
retrieving revision 1.1.2.1.2.1
diff -u -r1.1.2.1.2.1 click_filter.info
--- statistics/click_filter/click_filter.info	11 Aug 2008 21:15:27 -0000	1.1.2.1.2.1
+++ statistics/click_filter/click_filter.info	21 Oct 2008 13:22:48 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = ad
 description = Filter duplicate click statistics.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-08-12
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1218499216"
+
Index: statistics/click_filter/click_filter.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/statistics/click_filter/Attic/click_filter.install,v
retrieving revision 1.1.2.3.2.3
diff -u -r1.1.2.3.2.3 click_filter.install
--- statistics/click_filter/click_filter.install	26 Jan 2009 02:21:09 -0000	1.1.2.3.2.3
+++ statistics/click_filter/click_filter.install	22 Jan 2009 14:52:18 -0000
@@ -1,10 +1,9 @@
 <?php
-// $Id: click_filter.install,v 1.1.2.3.2.3 2009/01/26 02:21:09 jeremy Exp $
+// $Id: click_filter.install,v 1.1.2.3.2.1 2008/08/11 21:15:27 jeremy Exp $
 
 /**
+ * @file
  * Click_filter module database schema.
- * Copyright (c) 2008 Jeremy Andrews <jeremy@kerneltrap.org>.  
- * All rights reserved.
  */
 
 define('CLICK_NEW', 0);
@@ -41,7 +40,7 @@
         continue;
       }
       // Search for duplicate clicks.
-      $result = db_query("SELECT cid FROM {ad_clicks} WHERE aid = %d AND status = %d AND cid != %d AND (uid = %d OR hostname = '%s')", $ad->aid, CLICK_VALID, $click->cid, $click->uid, $click->hostname);
+      $result = db_query("SELECT cid FROM {ad_clicks} WHERE aid = %d AND status = %d AND cid <> %d AND (uid = %d OR hostname = '%s')", $ad->aid, CLICK_VALID, $click->cid, $click->uid, $click->hostname);
       if (db_result($result)) {
         // Mark as duplicate click.
         db_query('UPDATE {ad_clicks} SET status = %d WHERE cid = %d', CLICK_DUPLICATE, $click->cid);
@@ -55,7 +54,7 @@
       }
     }
   }
-  drupal_set_message(t('Updated existing clicks: %valid valid, filtered %duplicate duplicate, %owner by owner and %bot by search engine bots.', array('%valid' => $valid, '%duplicate' => $duplicate, '%owner' => $owner, '%bot' => $bot)));
+  drupal_set_message(st('Updated existing clicks: %valid valid, filtered %duplicate duplicate, %owner by owner and %bot by search engine bots.', array('%valid' => $valid, '%duplicate' => $duplicate, '%owner' => $owner, '%bot' => $bot)));
 }
 
 /**
Index: statistics/click_filter/click_filter.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/statistics/click_filter/Attic/click_filter.module,v
retrieving revision 1.1.2.3.2.3
diff -u -r1.1.2.3.2.3 click_filter.module
--- statistics/click_filter/click_filter.module	26 Jan 2009 02:21:09 -0000	1.1.2.3.2.3
+++ statistics/click_filter/click_filter.module	26 Jan 2009 13:54:14 -0000
@@ -1,12 +1,9 @@
 <?php
-// $Id: click_filter.module,v 1.1.2.3.2.3 2009/01/26 02:21:09 jeremy Exp $
+// $Id: click_filter.module,v 1.1.2.3.2.1 2008/08/11 21:15:27 jeremy Exp $
 
 /**
 * @file
  * Filter duplicate and other unwanted clicks from ad statistics.
- *
- * Copyright (c) 2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.
  */
 
 /**
@@ -83,7 +80,7 @@
 }
 
 /** 
- * Drupal _perm hook.
+ * Implementation of hook_perm().
  */
 function click_filter_perm() {
   return array('view filtered clicks', 'filter clicks');
Index: text/ad_text.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/text/Attic/ad_text.info,v
retrieving revision 1.1.2.2.2.1
diff -u -r1.1.2.2.2.1 ad_text.info
--- text/ad_text.info	11 Aug 2008 21:15:27 -0000	1.1.2.2.2.1
+++ text/ad_text.info	20 Nov 2008 00:00:38 -0000
@@ -3,4 +3,10 @@
 package = Ad
 dependencies[] = ad
 description = Enhances the ad module to support static text ads.
-core = 6.x
\ No newline at end of file
+core = 6.x
+; Information added by drupal.org packaging script on 2008-11-20
+version = "6.x-1.x-dev"
+core = "6.x"
+project = "ad"
+datestamp = "1227139237"
+
Index: text/ad_text.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/text/ad_text.install,v
retrieving revision 1.2.2.2.2.4.2.1
diff -u -r1.2.2.2.2.4.2.1 ad_text.install
--- text/ad_text.install	11 Aug 2008 21:15:27 -0000	1.2.2.2.2.4.2.1
+++ text/ad_text.install	27 Jan 2009 13:43:12 -0000
@@ -2,46 +2,51 @@
 // $Id: ad_text.install,v 1.2.2.2.2.4.2.1 2008/08/11 21:15:27 jeremy Exp $
 
 /**
+ * @file
  * Ad_text module database schema.
- * Copyright (c) 2005-2006 Jeremy Andrews <jeremy@kerneltrap.org>.  
- * All rights reserved.
  */
 
-function ad_text_install() {
-  switch ($GLOBALS['db_type']) {
-    case 'pgsql':
-      /**
-       * The ad_text table stores each line of the actual text ad.
-       */
-      db_query("CREATE TABLE {ad_text} (
-        aid INT NOT NULL DEFAULT '0' PRIMARY KEY,
-
-        url VARCHAR(255) NOT NULL DEFAULT '',
-        adheader VARCHAR(255) NOT NULL DEFAULT '',
-        adbody TEXT NOT NULL DEFAULT ''
-      );");
-      break;
-
-    case 'mysql':
-    case 'mysqli':
-    default:
-
-      /**
-       * The ad_text table stores each line of the actual text ad.
-       */
-      db_query("CREATE TABLE {ad_text} (
-        aid INT(10) UNSIGNED NOT NULL DEFAULT '0',
-
-        url VARCHAR(255) NOT NULL DEFAULT '',
-        adheader VARCHAR(255) NOT NULL DEFAULT '',
-        adbody TEXT NULL,
-
-        PRIMARY KEY (aid)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+/**
+ * Implementation of hook_schema().
+ */
+function ad_text_schema() {
+  $schema['ad_text'] = array(
+    'description' => 'The ad_text table stores sources of text ads.',
+    'fields' => array(
+      'aid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'url' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'adheader' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'adbody' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('aid'),
+  );
 
-  }
+  return $schema;
+}
 
-  drupal_set_message(t('The necessary ad_text module tables have been created.'));
+/**
+ * ad_text module installation.
+ */
+function ad_text_install() {
+  drupal_install_schema('ad_text');
 }
 
 /**
@@ -56,11 +61,4 @@
   while ($aid = db_result($result)) {
     node_delete($aid);
   }
-
-  // Drop all ad_text module tables.
 }
-
-/**
- * Convert to utf8 character set for all tables to allow for proper 
- * internationalization.
- */
Index: text/ad_text.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/text/ad_text.module,v
retrieving revision 1.2.2.7.2.24.2.2
diff -u -r1.2.2.7.2.24.2.2 ad_text.module
--- text/ad_text.module	19 Nov 2008 17:20:41 -0000	1.2.2.7.2.24.2.2
+++ text/ad_text.module	26 Jan 2009 13:53:37 -0000
@@ -4,9 +4,6 @@
 /**
  * @file
  * Enhances the ad module to support static text ads.
- *
- * Copyright (c) 2005-2008.
- *   Jeremy Andrews <jeremy@kerneltrap.org>.
  */
 
 /**
@@ -32,6 +29,10 @@
   return $string;
 }
 
+/**
+ * Implementation of hook_init().
+ * Include ad_image_views.inc if the views.module is enabled.
+ */
 function ad_text_init() {
   if (function_exists('drupal_set_content')) {
     if (module_exists('views')) {
@@ -52,7 +53,10 @@
   return $output;
 }
 
-function ad_text_global_settings($edit = array()) {
+/**
+ * Text ad settings form.
+ */
+function ad_text_global_settings($form_state) {
   $form = array();
 
   $form['header_min'] = array(
@@ -98,7 +102,7 @@
 }
 
 /**
- * 
+ * Settings form validation handler.
  */
 function ad_text_global_settings_validate($form, &$form_state) {
   if ((int)$form_state['values']['header_min'] > (int)$form_state['values']['header_max']) {
@@ -110,7 +114,7 @@
 }
 
 /**
- *
+ * Settings form submit handler.
  */
 function ad_text_global_settings_submit($form, &$form_state) {
   variable_set('header_min', $form_state['values']['header_min']);
@@ -119,6 +123,9 @@
   variable_set('body_max', $form_state['values']['body_max']);
 }
 
+/**
+ * Implementation of hook_adapi().
+ */
 function ad_text_adapi($op, &$node) {
   switch ($op) {
     case 'load':
@@ -152,7 +159,14 @@
       return ad_text_node_validate($node, $todo);
 
     case 'type':
-      return 'text';
+      return array(
+        'text' => array(
+          'name' => t('Text ad'),
+          'module' => 'ad_text',
+          'description' => t('A text advertisement.'),
+          'help' => t('A text advertisement.'),
+        ),
+      );
 
     case 'permissions':
       if (!isset($node->adtype) || $node->adtype == 'text') {
@@ -178,8 +192,7 @@
     '#value' => ad_text_display_ad($node),
   );
 
-  if (ad_adaccess($node->nid, 'manage ad text') ||
-      arg(1) == 'add' && user_access('create advertisements')) {
+  if (ad_adaccess($node, 'manage ad text') || arg(1) == 'add' && user_access('create advertisements')) {
     $form['ad_text']['url'] = array(
       '#type' => 'textfield',
       '#title' => t('Destination URL'),
@@ -208,6 +221,9 @@
   return $form;
 }
 
+/**
+ * Adapi helper function for displaying ad itself.
+ */
 function ad_text_node_view(&$node) {
   $node->content['ad'] = array(
     '#value' => theme('box', '', stripslashes(ad_text_display_ad($node))),
@@ -223,6 +239,9 @@
   );
 }
 
+/**
+ * Text ads node validator.
+ */
 function ad_text_node_validate($form, &$form_state) {
   // Enforce minimum and maximum lengths.
   $header_len = strlen($node->adheader);
@@ -247,5 +266,4 @@
   if (variable_get('ad_validate_url', 1) && (!valid_url($node->url, TRUE))) {
     form_set_error('url', t('You must specify a valid %field.', array('%field' => t('Destination URL'))));
   }
-}
-
+}
\ No newline at end of file
Index: text/ad_text_views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/ad/text/Attic/ad_text_views.inc,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 ad_text_views.inc
--- text/ad_text_views.inc	27 Jan 2007 19:59:41 -0000	1.1.2.1
+++ text/ad_text_views.inc	22 Jan 2009 14:32:56 -0000
@@ -42,5 +42,3 @@
     return "<a href=\"$ad->redirect\">$ad->adheader</a><br />$ad->adbody";
   }
 }
-
-?>
--- ad.admin.inc
+++ ad.admin.inc
@@ -0,0 +1,913 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Advertisement admin pages and functions.
+ */
+
+/**
+ * Build default ad administration page.
+ */
+function ad_admin_list() {
+  _ad_check_installation();
+
+  $output = drupal_get_form('ad_filter_form');
+
+  if ($_POST['operation'] == 'delete' && $_POST['ads']) {
+    return drupal_get_form('ad_multiple_delete_confirm');
+  }
+  $output .= drupal_get_form('ad_admin_ads');
+
+  return $output;
+}
+
+/**
+ * Provide a filterable list of advertisements.
+ */
+function ad_admin_ads() {
+  $filter = ad_build_filter_query();
+  $result = pager_query('SELECT a.*, n.* FROM {ads} a INNER JOIN {node} n ON a.aid = n.nid '. $filter['join'] .' '. $filter['where'] .' ORDER BY n.changed DESC', 50, 0, NULL, $filter['args']);
+
+  $form['options'] = array('#type' => 'fieldset',
+    '#title' => t('Update options'),
+    '#prefix' => '<div class="container-inline">',
+    '#suffix' => '</div>',
+  );
+  $options = array();
+  foreach (module_invoke_all('ad_operations') as $operation => $array) {
+    $options[$operation] = $array['label'];
+  }
+  $form['options']['operation'] = array('#type' => 'select', '#options' => $options,  '#default_value' => 'approve');
+  $form['options']['submit'] = array('#type' => 'submit', '#value' => t('Update'));
+
+  $destination = drupal_get_destination();
+  while ($ad = db_fetch_object($result)) {
+    $ads[$ad->aid] = '';
+    $form['title'][$ad->aid] = array('#value' => l($ad->title, 'node/'. $ad->aid));
+    $form['group'][$ad->aid] = array('#value' => _ad_get_group($ad->aid));
+    $form['adtype'][$ad->aid] = array('#value' => t($ad->adtype));
+    $form['adstatus'][$ad->aid] = array('#value' => t($ad->adstatus));
+    $form['operations'][$ad->aid] = array('#value' => l(t('edit'), 'node/'. $ad->aid .'/edit', array('query' => $destination)));
+  }
+  $form['ads'] = array('#type' => 'checkboxes', '#options' => $ads);
+  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
+  return $form;
+}
+
+/**
+ * Implementation of hook_ad_operations().
+ */
+function ad_ad_operations() {
+  $operations = array(
+    'approved' => array(
+      'label' => t('Mark as approved'),
+      'callback' => 'ad_operations_callback',
+      'callback arguments' => array('approved'),
+    ),
+    'active' => array(
+      'label' => t('Mark as active'),
+      'callback' => 'ad_operations_callback',
+      'callback arguments' => array('active'),
+    ),
+    'expired' => array(
+      'label' => t('Mark as expired'),
+      'callback' => 'ad_operations_callback',
+      'callback arguments' => array('expired'),
+    ),
+    'pending' => array(
+      'label' => t('Mark as pending'),
+      'callback' => 'ad_operations_callback',
+      'callback arguments' => array('pending'),
+    ),
+    'offline' => array(
+      'label' => t('Mark as offline'),
+      'callback' => 'ad_operations_callback',
+      'callback arguments' => array('offline'),
+    ),
+    'unpublished' => array(
+      'label' => t('Mark as unpublished'),
+      'callback' => 'ad_operations_callback',
+      'callback arguments' => array('unpublished'),
+    ),
+    'denied' => array(
+      'label' => t('Mark as denied'),
+      'callback' => 'ad_operations_callback',
+      'callback arguments' => array('denied'),
+    ),
+    'delete' => array(
+      'label' => t('Delete'),
+    ),
+  );
+  return $operations;
+}
+
+/**
+ * Callback function for admin mass approving ads.
+ * TODO: Update activated and expired when appropriate.
+ * TODO: Publish/unpublish nodes when appropriate.
+ */
+function ad_operations_callback($ads, $action) {
+  $placeholders = implode(',', array_fill(0, count($ads), '%d'));
+  db_query("UPDATE {ads} SET adstatus = '". $action ."' WHERE aid IN(". $placeholders .')', $ads);
+  foreach ($ads as $aid) {
+    $node = node_load($aid);
+    ad_statistics_increment($aid, 'update');
+    ad_statistics_increment($aid, $action);
+    // Allow ad type module to act on nodeapi events.  The adapi hook provides
+    // access to additional variables not available in the nodeapi hook.
+    if ($node->adtype) {
+      // Don't use module_invoke, as in pre-PHP5 the changes to $node won't be
+      // passed back.
+      $function = "ad_$node->adtype" .'_adapi';
+      if (function_exists($function)) {
+        $function($op, $node);
+      }
+    }
+    // Allow ad cache module to act on nodeapi events.
+    $cache = variable_get('ad_cache', 'none');
+    if ($cache != 'none') {
+      $function = "ad_cache_$cache" .'_adcacheapi';
+      if (function_exists($function)) {
+        $function($action, $node);
+      }
+    }
+  }
+}
+
+/**
+ * Display a form to confirm whether to really delete the selected ads.
+ */
+function ad_multiple_delete_confirm($form_state) {
+  $form['ads'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
+  // array_filter returns only elements with TRUE values
+  foreach (array_filter($form_state['values']['ads']) as $aid => $value) {
+    $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $aid));
+    $form['ads'][$aid] = array('#type' => 'hidden', '#value' => $aid, '#prefix' => '<li>', '#suffix' => check_plain($title) ."</li>\n");
+  }
+  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+
+  return confirm_form($form,
+                      t('Are you sure you want to delete these ads?'),
+                      'admin/content/ad', t('This action cannot be undone.'),
+                      t('Delete all'), t('Cancel'));
+}
+
+/**
+ * Perform the actual ad deletions.
+ */
+function ad_multiple_delete_confirm_submit($form, &$form_state) {
+  if ($form_state['values']['confirm']) {
+    foreach ($form_state['values']['ads'] as $aid => $value) {
+      node_delete($aid);
+    }
+    drupal_set_message(t('The ads have been deleted.'));
+  }
+  $form_state['redirect'] = 'admin/content/ad';
+}
+
+/**
+ * Theme ad administration overview.
+ */
+function theme_ad_admin_ads($form) {
+  // Overview table:
+  $header = array(theme('table_select_header_cell'), t('Title'), t('Group'), t('Type'), t('Status'), t('Operations'));
+
+  $output .= drupal_render($form['options']);
+  if (isset($form['title']) && is_array($form['title'])) {
+    foreach (element_children($form['title']) as $key) {
+      $row = array();
+      $row[] = drupal_render($form['ads'][$key]);
+      $row[] = drupal_render($form['title'][$key]);
+      $row[] = drupal_render($form['group'][$key]);
+      $row[] = drupal_render($form['adtype'][$key]);
+      $row[] = drupal_render($form['adstatus'][$key]);
+      $row[] = drupal_render($form['operations'][$key]);
+      $rows[] = $row;
+    }
+
+  }
+  else  {
+    $rows[] = array(array('data' => t('No ads available.'), 'colspan' => '6'));
+  }
+
+  $output .= theme('table', $header, $rows);
+  if ($form['pager']['#value']) {
+    $output .= drupal_render($form['pager']);
+  }
+
+  $output .= drupal_render($form);
+
+  return $output;
+}
+
+/**
+ * Must select an ad if performing an operation.
+ */
+function ad_admin_ads_validate($form, &$form_state) {
+  $ads = array_filter($form_state['values']['ads']);
+  if (count($ads) == 0) {
+    form_set_error('', t('No ads selected.'));
+  }
+}
+
+/**
+ * Submit the ad administration update form.
+ */
+function ad_admin_ads_submit($form, &$form_state) {
+  $operations = module_invoke_all('ad_operations');
+  $operation = $operations[$form_state['values']['operation']];
+  // Filter out unchecked nodes
+  $ads = array_filter($form_state['values']['ads']);
+  if ($function = $operation['callback']) {
+    // Add in callback arguments if present.
+    if (isset($operation['callback arguments'])) {
+      $args = array_merge(array($ads), $operation['callback arguments']);
+    }
+    else {
+      $args = array($ads);
+    }
+    call_user_func_array($function, $args);
+
+    cache_clear_all();
+    drupal_set_message(t('The update has been performed.'));
+  }
+}
+
+/**
+ * Build query for ad administration filters based on session.
+ */
+function ad_build_filter_query() {
+  $filters = ad_filters();
+
+  // Build query
+  $where = $args = array();
+  $join = '';
+  foreach ($_SESSION['ad_overview_filter'] as $index => $filter) {
+    list($key, $value) = $filter;
+    switch ($key) {
+      case 'status':
+        list($value, $key) = explode('-', $value, 2);
+        $op = $key == 1 ? '=' : '!=';
+        $where[] = "a.adstatus $op '%s'";
+        break;
+      case 'group':
+        $table = "tn$index";
+        $where[] = "$table.tid = %d";
+        $join .= "INNER JOIN {term_node} $table ON n.nid = $table.nid ";
+        break;
+      case 'type':
+        $where[] = "a.adtype = '%s'";
+    }
+    $args[] = $value;
+  }
+  $where = count($where) ? 'WHERE '. implode(' AND ', $where) : '';
+
+  return array('where' => $where, 'join' => $join, 'args' => $args);
+}
+
+/**
+ * List ad administration filters that can be applied.
+ */
+function ad_filters() {
+  $session = &$_SESSION['ad_overview_filter'];
+  $session = is_array($session) ? $session : array();
+  // Regular filters
+  $options = array();
+  $options = array(
+    'pending-1' => t('pending'),
+    'approved-1' => t('approved'),
+    'active-1' => t('active'),
+    'offline-1' => t('offline'),
+    'unpublished-1' => t('unpublished'),
+    'expired-1' => t('expired'),
+    'denied-1' => t('denied'),
+    'pending-0' => t('not pending'),
+    'approved-0' => t('not approved'),
+    'active-0' => t('not active'),
+    'offline-0' => t('not offline'),
+    'unpublished-0' => t('not unpublished'),
+    'expired-0' => t('not expired'),
+    'denied-0' => t('not denied')
+  );
+
+  $filters['status'] = array(
+    'title' => t('status'),
+    'options' => $options
+  );
+  $adtypes = ad_get_types();
+  $filters['type'] = array(
+    'title' => t('type'),
+    'options' => $adtypes,
+  );
+  // The taxonomy filter
+  if ($taxonomy = module_invoke('taxonomy', 'get_tree', _ad_get_vid())) {
+    $options = array();
+    // TODO: Add support for the default group.
+    //$options[0] = t('default');
+    foreach ($taxonomy as $term) {
+      $options[$term->tid] = $term->name;
+    }
+    $filters['group'] = array('title' => t('group'), 'options' => $options);
+  }
+
+  return $filters;
+}
+
+/**
+ * Theme ad administration filter selector.
+ */
+function theme_ad_filters($form) {
+  $output .= '<ul class="clear-block">';
+  if (sizeof($form['current'])) {
+    foreach (element_children($form['current']) as $key) {
+      $output .= '<li>'. drupal_render($form['current'][$key]) .'</li>';
+    }
+  }
+
+  $output .= '<li><dl class="multiselect">'. (sizeof($form['current']) ? '<dt><em>'. t('and') .'</em> '. t('where') .'</dt>' : '') .'<dd class="a">';
+  foreach (element_children($form['filter']) as $key) {
+    $output .= drupal_render($form['filter'][$key]);
+  }
+  $output .= '</dd>';
+
+  $output .= '<dt>'. t('is') .'</dt><dd class="b">';
+
+  foreach (element_children($form['status']) as $key) {
+    $output .= drupal_render($form['status'][$key]);
+  }
+  $output .= '</dd>';
+
+  $output .= '</dl>';
+  $output .= '<div class="container-inline" id="ad-admin-buttons">'. drupal_render($form['buttons']) .'</div>';
+  $output .= '</li></ul>';
+
+  return $output;
+}
+
+/**
+ * Return form for advertisement administration filters.
+ */
+function ad_filter_form($form_state) {
+  $session = &$_SESSION['ad_overview_filter'];
+  $session = is_array($session) ? $session : array();
+  $filters = ad_filters();
+
+  $i = 0;
+  $form['filters'] = array('#type' => 'fieldset',
+    '#title' => t('Show only ads where'),
+    '#theme' => 'ad_filters',
+  );
+  foreach ($session as $filter) {
+    list($type, $value) = $filter;
+    if ($type == 'category') {
+      // Load term name from DB rather than search and parse options array.
+      $value = module_invoke('taxonomy', 'get_term', $value);
+      $value = $value->name;
+    }
+    else if ($type == 'status') {
+      $value = $filters['status']['options'][$value];
+    }
+    else {
+      $value = $filters[$type]['options'][$value];
+    }
+    $string = ($i++ ? '<em>and</em> where <strong>%a</strong> is <strong>%b</strong>' : '<strong>%a</strong> is <strong>%b</strong>');
+    $form['filters']['current'][] = array('#value' => t($string, array('%a' => $filters[$type]['title'] , '%b' => $value)));
+    if ($type == 'type') {
+      // Remove the type option if it is already being filtered on.
+      unset($filters['type']);
+    }
+    else if ($type == 'group') {
+      unset($filters['group']);
+    }
+    if ($type == 'status') {
+      foreach ($session as $option) {
+        if ($option[0] == 'status') {
+          list($value, $key) = explode('-', $option[1], 2);
+          if ($key) {
+            // One postive key means we can't have any more.
+            // Remove the status option if we're already filtering on a positive
+            // key (ie, 'active', as an ad can't be 'active' and 'pending')
+            unset($filters['status']);
+          }
+          else {
+            // When a key is selected, remove it and its inverse as there's
+            // no logic in selecting the same key multiple times, and selecting
+            // two opposite keys will always return 0 results.
+            $inverse = $key == 1 ? 0 : 1;
+            unset($filters['status']['options'][$option[1]]);
+            unset($filters['status']['options'][$value .'-'. $inverse]);
+          }
+        }
+      }
+    }
+  }
+
+  foreach ($filters as $key => $filter) {
+    $names[$key] = $filter['title'];
+    $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']);
+  }
+
+  $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status');
+  $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter')));
+  if (count($session)) {
+    $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo'));
+    $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
+  }
+
+  return $form;
+}
+
+/**
+ * Theme ad administration filter form.
+ */
+function theme_ad_filter_form($form) {
+  $output .= '<div id="ad-admin-filter">';
+  $output .= drupal_render($form['filters']);
+  $output .= '</div>';
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Process result from ad administration filter form.
+ */
+function ad_filter_form_submit($form, &$form_state) {
+  $filters = ad_filters();
+/* TODO The 'op' element in the form values is deprecated.
+   Each button can have #validate and #submit functions associated with it.
+   Thus, there should be one button that submits the form and which invokes
+   the normal form_id_validate and form_id_submit handlers. Any additional
+   buttons which need to invoke different validate or submit functionality
+   should have button-specific functions. */
+  switch ($form_state['values']['op']) {
+    case t('Filter'):
+    case t('Refine'):
+      if (isset($form_state['values']['filter'])) {
+        $filter = $form_state['values']['filter'];
+
+        // Flatten the options array to accommodate hierarchical/nested options.
+        $flat_options = form_options_flatten($filters[$filter]['options']);
+
+        if (isset($flat_options[$form_state['values'][$filter]])) {
+          $_SESSION['ad_overview_filter'][] = array($filter, $form_state['values'][$filter]);
+        }
+      }
+      break;
+    case t('Undo'):
+      array_pop($_SESSION['ad_overview_filter']);
+      break;
+    case t('Reset'):
+      $_SESSION['ad_overview_filter'] = array();
+      break;
+  }
+}
+
+
+/**
+ *
+ */
+function ad_admin_statistics($form_state) {
+  $groups = ad_groups_list(TRUE);
+  foreach ($groups as $tid => $group) {
+    if ($tid) {
+      $ads = db_result(db_query("SELECT count(aid) FROM {ads} a JOIN {term_node} t ON a.aid = t.nid WHERE t.tid = %d AND adstatus = 'active'", $tid));
+      $filter = "= ". $tid;
+    }
+    else {
+      $ads = db_result(db_query("SELECT count(aid) FROM {ads} a LEFT JOIN {term_node} t ON a.aid = t.nid WHERE t.tid IS NULL AND adstatus = 'active'"));
+      $filter = "IS NULL";
+    }
+    if (!$ads) {
+      continue;
+    }
+
+    $form[$group->name] = array(
+      '#type' => 'fieldset',
+      '#title' => $group->name,
+      '#collapsible' => TRUE,
+    );
+
+    // Get overall global statistics.
+    $statistics['global']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND n.tid %s", $filter));
+    $statistics['global']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND n.tid %s", $filter));
+
+    // Get overall statistics for this year and last year.
+    $this_year = date('Y000000');
+    $last_year = date('Y') - 1 .'000000';
+    $statistics['last_year']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND date <= %d AND n.tid %s", $last_year, $this_year, $filter));
+    $statistics['last_year']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND date <= %d AND n.tid %s", $last_year, $this_year, $filter));
+    $statistics['this_year']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $this_year, $filter));
+    $statistics['this_year']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $this_year, $filter));
+
+    // Get statistics for this month and last month.
+    $this_month = date('Ym0000');
+    $last_month = date('m') - 1;
+    if ($last_month == 0) {
+      $last_month = date('Y') - 1 .'120000';
+    }
+    else {
+      $last_month = date('Y') . ($last_month < 10 ? '0' : '') . $last_month .'0000';
+    }
+    $statistics['last_month']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND date <= %d AND n.tid %s", $last_month, $this_month, $filter));
+    $statistics['last_month']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND date <= %d AND n.tid %s", $last_month, $this_month, $filter));
+    $statistics['this_month']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $this_month, $filter));
+    $statistics['this_month']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $this_month, $filter));
+
+    // Get statistics for this week.
+    $this_week_start = date('Ymd00', time() - 60*60*24*6);
+    $statistics['this_week']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $this_week_start, $filter));
+    $statistics['this_week']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $this_week_start, $filter));
+
+    // Get statistics for yesterday and today.
+    $yesterday_start = date('Ymd00', time() - 60*60*24);
+    $yesterday_end = date('Ymd24', time() - 60*60*24);
+    $today_start = date('Ymd00', time());
+    $statistics['yesterday']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND date <= %d AND n.tid %s", $yesterday_start, $yesterday_end, $filter));
+    $statistics['yesterday']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND date <= %d AND n.tid %s", $yesterday_start, $yesterday_end, $filter));
+    $statistics['today']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date >= %d AND n.tid %s", $today_start, $filter));
+    $statistics['today']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date >= %d AND n.tid %s", $today_start, $filter));
+
+    // Get statistics for this hour and the last hour.
+    $last_hour = date('YmdH', time() - 60*60);
+    $this_hour = date('YmdH', time());
+    $statistics['last_hour']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date = %d AND n.tid %s", $last_hour, $filter));
+    $statistics['last_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date = %d AND n.tid %s", $last_hour, $filter));
+    $statistics['this_hour']['views'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'view' AND date = %d AND n.tid %s", $this_hour, $filter));
+    $statistics['this_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(s.count) FROM {ad_statistics} s LEFT JOIN {ads} a ON s.aid = a.aid LEFT JOIN {term_node} n ON a.aid = n.nid WHERE action = 'click' AND date = %d AND n.tid %s", $this_hour, $filter));
+
+    // TODO: Create this view and remove the && FALSE to enable this code.
+    if (module_exists('views') && FALSE) {
+      $form[$group->name]['statistics'] = array(
+        '#type' => 'markup',
+        '#value' => '<p>'. format_plural($ads, 'There is <a href="!url">1 active ad</a> in this group.', 'There are <a href="!url">%count</a> active ads in this group.', array('!url' => url('ad/'. $group->tid .'/group'))) .'</p>'. theme('ad_statistics_display', $statistics),
+      );
+    }
+    else {
+      $form[$group->name]['statistics'] = array(
+        '#type' => 'markup',
+        '#value' => '<p>'. format_plural($ads, 'There is 1 active ad in this group.', 'There are @count active ads in this group.') .'</p>'. theme('ad_statistics_display', $statistics),
+      );
+    }
+  }
+
+  if (count($form) == 0) {
+    $form['header'] = array(
+      '#type' => 'markup',
+      '#value' => '<p>'. t('There are no active ads.') .'</p>',
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Display a form for the ad module settings.
+ */
+function ad_admin_configure_settings($form_state) {
+  _ad_check_installation();
+
+  $adserve = variable_get('adserve', '');
+  $adserveinc = variable_get('adserveinc', '');
+  $form['configuration'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Status'),
+  );
+  $form['configuration']['adserve'] = array(
+    '#type' => 'markup',
+    '#value' => t('Using detected adserve scripts: %adserve, %adserveinc', array('%adserve' => ($adserve ? $adserve : t('not found')), '%adserveinc' => ($adserveinc ? $adserveinc : t('not found')))),
+  );
+
+  $form['general'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('General'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+
+  // TODO: This needs a per-group over-ride, in case some groups are IFrames,
+  // while others are JavaScript, etc.
+  $form['general']['ad_link_target'] = array(
+    '#type' => 'radios',
+    '#title' => t('Click-through target'),
+    '#options' => array(
+      '_self' => t('same browser window and frame'),
+      '_blank' => t('new browser window'),
+      '_parent' => t('parent frame'),
+      '_top' => t('same browser window, removing all frames'),
+    ),
+    '#default_value' => variable_get('ad_link_target', '_self'),
+    '#description' => t('Select an option above to configure what happens when an ad is clicked.  These options set the <em>a target</em>, and are <em>_self</em>, <em>_blank</em>, <em>_parent</em> and <em>_top</em> respectively.'),
+  );
+
+  $form['general']['ad_link_nofollow'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('nofollow'),
+    '#default_value' => variable_get('ad_link_nofollow', 0),
+    '#description' => t('If enabled, %tag will be added to advertisement links generated by this module.', array('%tag' => t('rel="nofollow"'))),
+  );
+
+  // Provide hook for ad_display_TYPE modules to set display TYPE.
+  $display_options = array_merge(array('javascript' => t('JavaScript'), 'jquery' => t('jQuery'), 'iframe' => t('IFrame'), 'raw' => t('Raw')), module_invoke_all('displayapi', 'display_method'), array());
+
+  // Provide hook for ad_display_TYPE modules to define inline description.
+  $description = t('This setting configures the default method for displaying advertisements on your website.  It is possible to override this setting when making direct calls to ad(), as described in the documentation.  Using the JavaScript, jQuery, and IFrame display methods allows you to display random ads and track views even on cached pages.  When using the Raw display method together with Drupal\'s page cache, view statistics will be properly tracked but advertisements will only change when the page cache is updated.');
+  $return = module_invoke_all('displayapi', 'display_description', array());
+  foreach ($return as $describe) {
+    $description .= ' '. $describe;
+  }
+
+  $form['general']['ad_display'] = array(
+    '#type' => 'radios',
+    '#title' => t('Display type'),
+    '#default_value' => variable_get('ad_display', 'javascript'),
+    '#options' => $display_options,
+    '#description' => $description,
+  );
+
+  $form['general']['ad_validate_url'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Validate URLs'),
+    '#default_value' => variable_get('ad_validate_url', 1),
+    '#description' => t('If enabled, any destination URLs entered in ads will be required to be complete URLs (including http:// or https:// at the beginning).  If you wish to include internal urls, you will need to disable this option.'),
+  );
+
+  $form['general']['ad_filter'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Filter ads'),
+    '#default_value' => variable_get('ad_filter', 0),
+    '#description' => t('If enabled, the input format for each advertisement node will be applied to the displayed advertisement.'),
+  );
+
+  $form['iframe'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('IFrame'),
+    '#collapsible' => TRUE,
+    '#collapsed' => variable_get('ad_display', 'javascript') == 'iframe' ? FALSE : TRUE
+  );
+  $form['iframe']['ad_iframe_frameborder'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Frameborder'),
+    '#default_value' => variable_get('ad_iframe_frameborder', 0),
+    '#description' => t('If enabled, IFrames used for displaying ads will have a frameborder.'),
+  );
+  $form['iframe']['ad_iframe_scroll'] = array(
+    '#type' => 'radios',
+    '#title' => t('Scrolling'),
+    '#default_value' => variable_get('ad_iframe_scroll', 'auto'),
+    '#options' => array('auto' => 'auto', 'on' => 'on', 'off' => 'off'),
+    '#description' => t('Define whether or not scroll bars should be enabled for the ad IFrame.'),
+  );
+  $form['iframe']['ad_iframe_width'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Width'),
+    '#default_value' => variable_get('ad_iframe_width', ''),
+    '#maxlength' => 8,
+    '#size' => 5,
+    '#required' => FALSE,
+    '#description' => t('The default width for advertisement IFrames'),
+  );
+  $form['iframe']['ad_iframe_height'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Height'),
+    '#default_value' => variable_get('ad_iframe_height', ''),
+    '#maxlength' => 8,
+    '#size' => 5,
+    '#required' => FALSE,
+    '#description' => t('The default height for advertisement IFrames'),
+  );
+
+  $form['cache'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Cache'),
+    '#collapsible' => TRUE,
+    '#collapsed' => variable_get('ad_cache', 'none') == 'none' ? TRUE : FALSE,
+  );
+
+  // Provide hook for ad_cache_TYPE modules to set cache TYPE.
+  $cache_options = array_merge(array('none' => t('None')), module_invoke_all('adcacheapi', 'method', array()));
+
+  // Provide hook for ad_cache_TYPE modules to define inline description.
+  $description = t('A cache can be used to efficiently track how many times advertisements are displayed and clicked.');
+  $return = module_invoke_all('adcacheapi', 'description', array());
+  foreach ($return as $describe) {
+    $description .= ' '. $describe;
+  }
+
+  $form['cache']['ad_cache'] = array(
+    '#type' => 'radios',
+    '#title' => t('Type'),
+    '#default_value' => variable_get('ad_cache', 'none'),
+    '#options' => $cache_options,
+    '#description' => $description,
+  );
+
+  // Provide hook for ad_cache_TYPE modules to add inline settings.
+  $form['cache'] = array_merge($form['cache'], module_invoke_all('adcacheapi', 'settings', $form_state['values']));
+
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Validate form settings, calling attention to any illogical configurations.
+ */
+function ad_admin_configure_settings_validate($form, &$form_state) {
+  if ($form_state['values']['ad_link_target'] == '_self' &&
+      $form_state['values']['ad_display'] == 'iframe') {
+    // We don't consider this an error, as this could be exactly what the
+    // administrator is trying to do.  But as for most people it is likely
+    // to be a misconfiguration, display a helpful warning...
+    drupal_set_message(t('You have configured your advertisements to be displayed in iframes, and you have configured your click-through target as "same browser window and frame".  This is an unusual configuration, as when you click your advertisements only the IFrame will be redirected.  Be sure that this is actually what you are trying to do.'));
+  }
+}
+
+/**
+ * Save updated values from settings form.
+ */
+function ad_admin_configure_settings_submit($form, &$form_state) {
+  variable_set('ad_link_target', $form_state['values']['ad_link_target']);
+  variable_set('ad_link_nofollow', $form_state['values']['ad_link_nofollow']);
+  variable_set('ad_cache', $form_state['values']['ad_cache']);
+  variable_set('ad_display', $form_state['values']['ad_display']);
+  variable_set('ad_validate_url', $form_state['values']['ad_validate_url']);
+  variable_set('ad_filter', $form_state['values']['ad_filter']);
+  variable_set('ad_iframe_frameborder', $form_state['values']['ad_iframe_frameborder']);
+  variable_set('ad_iframe_scroll', $form_state['values']['ad_iframe_scroll']);
+  variable_set('ad_iframe_width', $form_state['values']['ad_iframe_width']);
+  variable_set('ad_iframe_height', $form_state['values']['ad_iframe_height']);
+  if (($cache = variable_get('ad_cache', 'none')) != 'none') {
+    // Allow external cache types to store their settings
+    module_invoke('ad_cache_'. $cache, 'adcacheapi', 'settings_submit', $form_state['values']);
+  }
+/*
+ // TODO: Write an external display module and implement this.
+  $display = variable_get('ad_display', 'javascript');
+  if ($display != 'javascript' && $display != 'raw') {
+    // Allow external display types to store their settings
+    module_invoke('ad_cache_'. $cache, 'adcacheapi', 'settings_submit', $form_state['values']);
+  }*/
+}
+
+/**
+ * Empty page for ad_type modules that don't define a global settings page.
+ * This way admins can still set default permissions for this ad type.
+ */
+function ad_no_global_settings($form_state) {
+  $form = array();
+
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+function ad_admin_groups_list() {
+  _ad_check_installation();
+
+  $header = array(
+      array('data' => t('Name'), 'field' => 'name'),
+      array('data' => t('Description'), 'field' => 'description'),
+      array('data' => t('Options')),
+    );
+
+  $groups = taxonomy_get_tree(_ad_get_vid());
+
+  if ($groups != array()) {
+    foreach ($groups as $group) {
+      $row = array();
+      $row[] = $group->name;
+      $row[] = $group->description;
+      $row[] = l(t('edit'), "admin/content/ad/groups/$group->tid/edit");
+      $rows[] = $row;
+    }
+  }
+  else {
+    $rows[] = array(array('data' => t('No groups have been created.'), 'colspan' => 3));
+  }
+
+  $output = theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 15, 0);
+
+  return $output;
+}
+
+/**
+ * Returns a form for adding an ad group.
+ */
+function ad_admin_group_form($form_state, $group = NULL) {
+  $form['name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Group name'),
+    '#default_value' => $group->name,
+    '#maxlength' => 64,
+    '#required' => TRUE,
+    '#description' => t('Specify a name for the ad group.')
+  );
+
+  $form['description'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Description'),
+    '#default_value' => $group->description,
+    '#required' => TRUE,
+    '#description' => t('Describe this ad group.')
+  );
+
+  $form['weight'] = array(
+    '#type' => 'weight',
+    '#title' => t('Weight'),
+    '#default_value' => $group->weight ? $group->weight : 0,
+    '#description' => t('When listing ad groups, those with lighter (smaller) weights get listed before ad groups with heavier (larger) weights.  Ad groups with equal weights are sorted alphabetically.')
+  );
+
+  $form['vid'] = array(
+    '#type' => 'hidden',
+    '#value' => _ad_get_vid(),
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Create group'),
+  );
+
+  if ($group->tid) {
+    $form['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+    );
+    $form['tid'] = array(
+      '#type' => 'value',
+      '#value' => $group->tid
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Save a newly created ad group.
+ */
+function ad_admin_group_form_validate($form, &$form_state) {
+  if ($form_state['values']['op'] == t('Delete')) {
+    drupal_goto('admin/content/ad/groups/'. $form_state['values']['tid'] .'/delete');
+  }
+}
+
+
+/**
+ * Save a newly created ad group.
+ */
+function ad_admin_group_form_submit($form, &$form_state) {
+  $status = taxonomy_save_term($form_state['values']);
+  switch ($status) {
+    case SAVED_NEW:
+      $groups = variable_get('ad_groups', array());
+      $groups[] = $form_state['values']['tid'];
+      variable_set('ad_groups', $groups);
+      drupal_set_message(t('Created new ad group %term.', array('%term' => $form_state['values']['name'])));
+      break;
+    case SAVED_UPDATED:
+      drupal_set_message(t('The ad group %term has been updated.', array('%term' => $form_state['values']['name'])));
+  }
+  $form_state['redirect'] = 'admin/content/ad/groups';
+}
+
+/**
+ * Returns a confirmation page when deleting an ad group and all of its ads.
+ */
+function ad_confirm_group_delete($form_state, $group = NULL) {
+  $form['tid'] = array(
+    '#type' => 'value',
+    '#value' => $group->tid
+  );
+  $form['name'] = array(
+    '#type' => 'value',
+    '#value' => $group->name
+  );
+
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the ad group %name?', array('%name' => $group->name)),
+    'admin/content/ad/groups',
+    t('Ads that were within this group will not be deleted.  This action cannot be undone.'),
+    t('Delete'),
+    t('Cancel'));
+}
+
+/**
+ * Delete ad group.
+ */
+function ad_confirm_group_delete_submit($form, &$form_state) {
+  taxonomy_del_term($form_state['values']['tid']);
+  drupal_set_message(t('The ad group %term has been deleted.', array('%term' => $form_state['values']['name'])));
+  watchdog('ad', 'mailarchive: deleted %term ad group.', array('%term' => $form_state['values']['name']));
+
+  $form_state['redirect'] = 'admin/content/ad/groups';
+}
+

--- ad.pages.inc
+++ ad.pages.inc
@@ -0,0 +1,247 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Advertisement nodes pages and forms.
+ */
+
+function theme_node_ad($node, $yield_form = TRUE) {
+  $output = '';
+  if (ad_adaccess($node, 'access statistics')) {
+    $output = theme('ad_status_display', $node);
+    $output .= theme('ad_statistics_display', ad_statistics($node->nid));
+  }
+  if (ad_adaccess($node, 'access click history')) {
+    $header = array(
+      array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
+      array('data' => t('IP address'), 'field' => 'hostname'),
+      array('data' => t('URL where clicked'), 'field' => 'url'),
+    );
+    if (function_exists('click_filter_status_text') && user_access('view filtered clicks')) {
+      $header[] = array('data' => t('Status'), 'field' => 'status');
+    }
+    $header[] = '';
+
+    if ($node->nid) {
+      $sql = "SELECT cid, timestamp, uid, status, hostname, url FROM {ad_clicks} WHERE aid = %d";
+      $sql .= tablesort_sql($header);
+      $result = pager_query($sql, 25, 0, NULL, $node->nid);
+
+      while ($ad = db_fetch_object($result)) {
+        if (module_exists('click_filter') && $ad->status != CLICK_VALID) {
+          // Only show filtered clicks to users with permission to view them.
+          if (!user_access('view filtered clicks')) {
+            continue;
+          }
+        }
+        if (strlen($ad->url) > 40) {
+          $url = substr($ad->url, 0, 37) .'...';
+        }
+        else {
+          $url = $ad->url;
+        }
+        $row = array();
+        $click_user = user_load(array('uid' => $ad->uid));
+        $row[] = format_date($ad->timestamp, 'custom', 'M j H:i');
+        $row[] = $ad->hostname;
+        $row[] = l($url, $ad->url);
+        if (function_exists('click_filter_status_text') && user_access('view filtered clicks')) {
+          $row[] = click_filter_status_text($ad->status);
+        }
+        $row[] = '['. l(t('details'), 'node/$node->nid/details/'. $ad->cid) .']';
+        $rows[] = $row;
+      }
+
+      if (empty($rows)) {
+        $click_history = '<p>'. t('There are no clicks yet.') .'</p>';
+      }
+      else {
+        $click_history = theme('table', $header, $rows);
+      }
+      $click_history .= theme('pager', NULL, 25, 0);
+      $output .= theme('box', t('Click history'), $click_history);
+    }
+  }
+  if (variable_get('ad_filter', 0)) {
+    $output = check_markup($output, $node->format);
+  }
+  return $output;
+}
+
+/**
+ * Calculate statistics for the given advertisements.
+ * TODO: Introduce caching to make this more efficient.
+ */
+function ad_statistics($aid) {
+  // Get global statistics.
+  $statistics['global']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view'", $aid));
+  $statistics['global']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click'", $aid));
+
+  // No sense in making further queries if the ad has no global statistics.
+  if (!$statistics['global']['views'] && !$statistics['global']['clicks']) {
+    return $statistics;
+  }
+
+  // Get statistics for this year and last year.
+  $this_year = date('Y000000');
+  $last_year = date('Y') - 1 .'000000';
+  $statistics['last_year']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $aid, $last_year, $this_year));
+  $statistics['last_year']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $aid, $last_year, $this_year));
+  $statistics['this_year']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $aid, $this_year));
+  $statistics['this_year']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $aid, $this_year));
+
+  // No sense in making further queries if the ad has no statistics this year.
+  if (!$statistics['this_year']['views'] && !$statistics['this_year']['clicks']) {
+    return $statistics;
+  }
+
+  // Get statistics for this month and last month.
+  $this_month = date('Ym0000');
+  $last_month = date('m') - 1;
+  if ($last_month == 0) {
+    $last_month = date('Y') - 1 .'120000';
+  }
+  else {
+    $last_month = date('Y') . ($last_month < 10 ? '0' : '') . $last_month .'0000';
+  }
+  $statistics['last_month']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $aid, $last_month, $this_month));
+  $statistics['last_month']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $aid, $last_month, $this_month));
+  $statistics['this_month']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $aid, $this_month));
+  $statistics['this_month']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $aid, $this_month));
+
+  // No sense in making further queries if the ad has no statistics this month.
+  if (!$statistics['this_month']['views'] && !$statistics['this_month']['clicks']) {
+    return $statistics;
+  }
+
+  // Get statistics for this week.
+  $this_week_start = date('Ymd00', time() - 60*60*24*6);
+  $statistics['this_week']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date > %d", $aid, $this_week_start));
+  $statistics['this_week']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date > %d", $aid, $this_week_start));
+
+  // No sense in making further queries if the ad has no statistics this week.
+  if (!$statistics['this_week']['views'] && !$statistics['this_week']['clicks']) {
+    return $statistics;
+  }
+
+  // Get statistics for yesterday and today.
+  $yesterday_start = date('Ymd00', time() - 60*60*24);
+  $yesterday_end = date('Ymd24', time() - 60*60*24);
+  $today_start = date('Ymd00', time());
+  $statistics['yesterday']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $aid, $yesterday_start, $yesterday_end));
+  $statistics['yesterday']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $aid, $yesterday_start, $yesterday_end));
+  $statistics['today']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $aid, $today_start));
+  $statistics['today']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $aid, $today_start));
+
+  // No sense in making further queries if the ad has no statistics today.
+  if (!$statistics['today']['views'] && !$statistics['today']['clicks']) {
+    return $statistics;
+  }
+
+  // Get statistics for this hour and the last hour.
+  $last_hour = date('YmdH', time() - 60*60);
+  $this_hour = date('YmdH', time());
+  $statistics['last_hour']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date = %d", $aid, $last_hour));
+  $statistics['last_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date = %d", $aid, $last_hour));
+  $statistics['this_hour']['views'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date = %d", $aid, $this_hour));
+  $statistics['this_hour']['clicks'] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date = %d", $aid, $this_hour));
+
+  return $statistics;
+}
+
+function theme_ad_statistics_display($statistics) {
+  $header = array('', t('Views'), t('Clicks'), t('Click-thru'));
+  $rows = array();
+
+  $data = array(
+   'this_hour' => t('This hour'),
+   'last_hour' => t('Last hour'),
+   'today' => t('Today'),
+   'yesterday' => t('Yesterday'),
+   'this_week' => t('Last seven days'),
+   'last_week' => t('Last week'),
+   'this_month' => t('This month'),
+   'last_month' => t('Last month'),
+   'this_year' => t('This year'),
+   'last_year' => t('Last year'),
+   'global' => t('All time')
+  );
+
+  foreach ($data as $key => $value) {
+    if ($statistics[$key]['views'] || $statistics[$key]['clicks'] || $key == 'global') {
+      $rows[] = array(
+        array('data' => $value),
+        array('data' => (int)$statistics[$key]['views']),
+        array('data' => (int)$statistics[$key]['clicks']),
+        array('data' => $statistics[$key]['views'] ? sprintf('%1.2f', ((int)$statistics[$key]['clicks'] / (int)$statistics[$key]['views']) * 100) .'%' : '0.00%'),
+      );
+    }
+  }
+  if (empty($rows) || (!$statistics['global']['views'] && !$statistics['global']['clicks'])) {
+    $statistics = '<p>'. t('There are no any statistics yet.') .'</p>';
+  }
+  else {
+    $statistics = theme('table', $header, $rows);
+  }
+
+  return theme('box', t('Statistics'), $statistics);
+}
+
+
+function ad_click_details($node, $cid) {
+//  drupal_set_breadcrumb(array(l(t('Home'), NULL), l($node->title, "node/". $node->nid)));
+  if ($click = db_fetch_object(db_query('SELECT * FROM {ad_clicks} WHERE cid = %d', $cid))) {
+    $ad = node_load($click->aid);
+    $rows = array(
+      array(
+        array('data' => t('Time'), 'header' => TRUE),
+        format_date($click->timestamp, 'custom', 'D F j, Y h:i a'),
+      ),
+      array(
+        array('data' => t('IP Address'), 'header' => TRUE),
+        $click->hostname,
+      ),
+      array(
+        array('data' => t('User Agent'), 'header' => TRUE),
+        $click->user_agent,
+      ),
+      array(
+        array('data' => t('URL'), 'header' => TRUE),
+        l($click->url, $click->url),
+      ),
+      array(
+        array('data' => t('Advertisement'), 'header' => TRUE),
+        $ad->ad,
+      )
+    );
+    if (function_exists('click_filter_status_text') && user_access('view filtered clicks')) {
+      switch ($click->status) {
+        case 0:
+        default:
+          $status = t('Not valid: this click has not been counted for unknown reasons.  This is an unexpected error.');
+          break;
+        case 1:
+          $status = t('Valid: this is a valid click.');
+          break;
+        case 2:
+          $status = t('Not valid: this click has not been counted because another click by the same IP address was already counted.');
+          break;
+        case 3:
+          $status = t('Not valid: this click has not been counted because it was generated by an owner of the advertisement.');
+          break;
+        case 4:
+          $status = t('Not valid: this click has not been counted because it was generated by a user in a filtered role.');
+          break;
+        case 5:
+          $status = t('Not valid: this click has not been counted because it was generated by an automated "bot".');
+          break;
+      }
+      $rows[] = array(array('data' => t('Status'), 'header' => TRUE), $status);
+    }
+    $output = theme('table', array(), $rows, $attributes);
+  }
+  return $output;
+}
+
+

--- owners/ad_owners.info
+++ owners/ad_owners.info
@@ -0,0 +1,6 @@
+; $Id$
+name = Ad Owners
+package = Ad
+dependencies[] = ad
+description = Enhances the ad module to support ad owners.
+core = 6.x

--- owners/ad_owners.install
+++ owners/ad_owners.install
@@ -0,0 +1,83 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Ad_owners module database schema.
+ */
+
+/**
+ * Implementation of hook_schema().
+ */
+function ad_owners_schema() {
+  $schema['ad_owners'] = array(
+    'description' => 'Stores information about ad owners. Every ad can have one or more owners.',
+    'fields' => array(
+      'oid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'description' => 'Unique owner pair ID.',
+      ),
+      'aid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Ad id.',
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'The {users}.uid that owns ad.',
+      ),
+    ),
+    'primary key' => array('oid'),
+    'indexes' => array(
+      'aid' => array('aid'),
+      'uid' => array('uid'),
+    ),
+  );
+
+ /**
+  * Permissions can be granted to each owner of each ad.  The same owner
+  * can own multiple ads, and can have different permissions for each ad.
+  */
+  $schema['ad_permissions'] = array(
+    'description' => 'Permissions can be granted to each owner of each ad. The same owner can own multiple ads, and can have different permissions for each ad.',
+    'fields' => array(
+      'oid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0,
+        'description' => 'Owner pair ID.',
+      ),
+      'permissions' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big',
+        'description' => 'Ad permission info.',
+      ),
+    ),
+    'primary key' => array('oid'),
+  );
+
+  return $schema;
+}
+
+/**
+ * ad_external module installation.
+ */
+function ad_owners_install() {
+  drupal_install_schema('ad_owners');
+}
+
+/**
+ * Allow complete uninstallation of the ad_external module.
+ */
+function ad_owners() {
+  drupal_uninstall_schema('ad_owners');
+}

--- owners/ad_owners.module
+++ owners/ad_owners.module
@@ -0,0 +1,501 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Enhances the ad module to support ad owners.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function ad_owners_theme() {
+  return array(
+    'ad_owner_permissions_form' => array(
+      'arguments' => array(
+        'form' => NULL,
+      ),
+    ),
+  );
+};
+
+/**
+ * Implementation of hook_menu().
+ */
+function ad_owners_menu() {
+  $items = array();
+
+  $items['node/%node/adowners'] = array(
+    'title' => 'Ad owners',
+    'page callback' => 'ad_owners_overview',
+    'page arguments' => array(1),
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, 'manage owners'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 5,
+  );
+  $items['node/%node/adowners/list'] = array(
+    'title' => 'List',
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, 'manage owners'),
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => 0,
+  );
+  $items['node/%node/adowners/%user/permissions'] = array(
+    'title callback' => 'owner_permissions_title',
+    'title arguments' => array('!owner' => 3),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ad_owner_permissions_form', 1, 3),
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, 'manage owners'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 2,
+  );
+  $items['node/%node/adowners/%user/remove'] = array(
+    'title' => 'Remove owner',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ad_owner_remove_form', 1, 3),
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, 'manage owners'),
+    'type' => MENU_CALLBACK,
+    'weight' => 6,
+  );
+  $items['node/%node/adowners/add'] = array(
+    'title' => 'Add owner',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ad_owners_add_form', 1),
+    'access callback' => 'ad_adaccess',
+    'access arguments' => array(1, 'manage owners'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 4,
+  );
+
+  return $items;
+}
+
+/**
+ * Menu item title callback - use the user name
+ */
+function owner_permissions_title($account) {
+  return t('!owner\'s permissions', array('!owner' => $account->name));
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function ad_owners_form_alter(&$form, &$form_state, $form_id) {
+  if ($form_id == 'ad_'. arg(4) .'_global_settings' || $form_id == 'ad_no_global_settings') {
+    if (!isset($form['adtype'])) {
+      $form['adtype'] = array('#type' => 'value', '#value' => arg(4));
+    }
+    $permissions = module_invoke_all('adapi', 'permissions', NULL);
+    $form['permissions'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Permissions'),
+      '#collapsible' => TRUE,
+      '#description' => t('Select which permissions will be automatically granted to new owners of <em>!type</em> advertisements.', array('!type' => ad_get_types('name', arg(4)))),
+    );
+    $form['permissions']['default_permissions'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Default permissions for <em>!type</em> owners', array('!type' => ad_get_types('name', arg(4)))),
+      '#options' => drupal_map_assoc($permissions),
+      '#default_value' => variable_get('ad_'. arg(4) .'_default_permissions', array('access statistics', 'access click history', 'manage status')),
+    );
+    if (isset($form['save'])) {
+      $form['save']['#weight'] = 10;
+    }
+    $form['#submit'] = array('ad_global_settings_submit' => array()) + (array)$form['#submit'];
+  }
+}
+
+/**
+ * Implementation of hook_nodeapi().
+ */
+function ad_owners_nodeapi(&$node, $op, $teaser, $page) {
+  global $user;
+
+  switch ($op) {
+    case 'insert':
+    case 'update':
+      if ($node->adtype) {
+        // Be sure ad owner has at least default ad permissions.
+        ad_owners_add($node->nid, $node->uid);
+        ad_host_id_create($node->uid);
+      }
+      break;
+    case 'delete':
+      // Clean up ad_permissions and any other per-ad tables.
+      $result = db_query('SELECT oid, uid FROM {ad_owners} WHERE aid = %d', $node->nid);
+      while ($id = db_fetch_object($result)) {
+        db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $id->oid);
+        $owner = user_load(array('uid' => $id->uid));
+        // Tell plug-in modules to clean up.
+        module_invoke_all('adowners', 'remove', $id->oid, $owner);
+      }
+      db_query('DELETE FROM {ad_owners} WHERE aid = %d', $node->nid);
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_adapi().
+ */
+function ad_owners_adapi($op, $node = NULL) {
+  switch ($op) {
+    case 'permissions':
+      return array('manage owners');
+      break;
+  }
+}
+
+/**
+ * Determine whether the ad owner has a given privilege.
+ *
+ * @param $ad
+ *   Node object or aid of advertisement.
+ * @param $permission
+ *   Special Ad owners permission which should be checked (such as 'manage owners')
+ * @param $account
+ *   User object, which are accessing the ad or current user by default.
+ */
+function ad_owners_adaccess($ad, $permission, $account = NULL) {
+  global $user;
+  static $permissions = array();
+
+  if (!isset($account)) {
+    $account = $user;
+  }
+  
+  $aid = is_numeric($ad) ? $ad : $ad->nid;
+  if (!isset($permissions[$aid][$account->uid])) {
+    $oid = db_result(db_query("SELECT oid FROM {ad_owners} WHERE aid = %d and uid = %d", $aid, $account->uid));
+    $permissions[$aid][$account->uid] = explode('|,|', db_result(db_query("SELECT permissions FROM {ad_permissions} WHERE oid = %d", $oid)));
+  }
+  if (is_array($permission)) {
+    foreach ($permission as $perm) {
+      $access |= in_array($perm, $permissions[$aid][$account->uid]);
+    }
+  }
+  else {
+    $access = in_array($permission, $permissions[$aid][$account->uid]);
+  }
+
+  return $access;
+}
+
+
+/**
+ * TODO: Make this themeable.
+ * TODO: Group permissions by module.
+ * TODO: Allow modules to define default value for permission.
+ */
+function ad_owners_overview($node) {
+  drupal_set_title(t('Ad owners'));
+
+  // Be sure the node owner is listed as an ad owner
+  if (!db_result(db_query('SELECT oid FROM {ad_owners} WHERE uid = %d AND aid = %d', $node->uid, $node->nid))) {
+    ad_owners_add($node->nid, $node->uid);
+  }
+
+  $header = array(
+    array('data' => t('Username'), 'field' => 'uid'),
+    array('data' => t('Options')),
+  );
+
+  $sql = "SELECT a.uid, u.name FROM {ad_owners} a INNER JOIN {users} u ON a.uid = u.uid WHERE aid = %d";
+  $sql .= tablesort_sql($header);
+  $result = pager_query($sql, 25, 0, NULL, $node->nid);
+
+  $rows = array();
+  while ($owner = db_fetch_object($result)) {
+    $row = array();
+    $row[] = $owner->name;
+    $options = array();
+    // first option is 'permissions', plug-ins come afterwards
+    $options[] = l(t('permissions'), 'node/'. $node->nid .'/adowners/'. $owner->uid .'/permissions');
+    $options = array_merge($options, module_invoke_all('adowners', 'overview', $node->nid, $owner->uid));
+    // node owner has to remain an ad owner
+    if ($node->uid != $owner->uid) {
+      $options[] = l(t('remove'), 'node/'. $node->nid .'/adowners/'. $owner->uid .'/remove');
+    }
+    $options = implode(' | ', $options);
+    $row[] = $options;
+    $rows[] = $row;
+  }
+
+  $output = theme('table', $header, $rows);
+  $output .= theme('pager', NULL, 25, 0);
+
+  return $output;
+}
+
+/**
+ * A simple form for adding new users as owners of ads.
+ */
+function ad_owners_add_form($form_state, $node) {
+  $form = array();
+  drupal_set_title(t('Add owner'));
+
+  $form['aid'] = array(
+    '#type' => 'value',
+    '#value' => $node->nid,
+  );
+  $form['username'] = array(
+    '#autocomplete_path' => 'user/autocomplete',
+    '#description' => t('Enter the username of the user who should have ownership permissions on this advertisement.'),
+    '#required' => TRUE,
+    '#type' => 'textfield',
+    '#title' => t('Username'),
+  );
+  $form['save'] = array(
+    '#type' => 'submit',
+    '#value' => t('Add owner'),
+  );
+
+  return $form;
+}
+
+function ad_owners_add_form_validate($form, &$form_state) {
+  $owner = user_load(array('name' => $form_state['values']['username']));
+  if (!is_object($owner)) {
+    form_set_error('username', t('The specified username %username does not exist.', array('%username' => $form_state['values']['username'])));
+  }
+  else if (db_result(db_query('SELECT oid FROM {ad_owners} WHERE uid = %d AND aid = %d', $owner->uid, $form_state['values']['aid']))) {
+    form_set_error('username', t('The specified user %username is already an owner of this ad.', array('%username' => $form_state['values']['username'])));
+  }
+  else if (!user_access('edit own advertisements', $owner) &&
+           !user_access('administer advertisements', $owner)) {
+    form_set_error('username', t('The specified user %username does not have <em>edit own advertisements</em> nor <em>administer advertisements</em> permissions.  The user must be !assigned to a !role with these privileges before you can add them as an ad owner.', array('%username' => $form_state['values']['username'], '!assigned' => l(t('assigned'), "user/$owner->uid/edit"), '!role' => l(t('role'), 'admin/user/permissions'))));
+  }
+  module_invoke_all('adowners', 'validate', $owner, $form_state['values']['aid']);
+}
+
+function ad_owners_add_form_submit($form, &$form_state) {
+  $owner = user_load(array('name' => $form_state['values']['username']));
+  $node = node_load($form_state['values']['aid']);
+  if (!(ad_owners_add($node, $owner->uid))) {
+    form_set_error('username', t('The user is already an owner of the ad.'));
+  }
+  else {
+    drupal_set_message(t('The user %username has been added as an owner of this advertisement.', array('%username' => $form_state['values']['username'])));
+    drupal_goto('node/'. $form_state['values']['aid'] .'/adowners/'. $owner->uid .'/permissions');
+  }
+}
+
+function ad_is_owner($aid, $account = NULL) {
+  global $user;
+  if (!isset($account)) {
+    $account = $user;
+  }
+  if (db_result(db_query('SELECT oid FROM {ad_owners} WHERE uid = %d AND aid = %d', $account->uid, $aid))) {
+    return 1;
+  }
+  else {
+    return 0;
+  }
+}
+
+/**
+ * Add an owner to an ad.
+ */
+function ad_owners_add($node, $owner, $permissions = array()) {
+  $uid = is_numeric($owner) ? $owner : $owner->uid;
+  if ($GLOBALS['db_type'] == 'pgsql') {
+    db_query('START TRANSACTION;');
+  }
+  else {
+    // MySQL, MySQLi
+    db_query('LOCK TABLES {ad_owners} WRITE');
+  }
+
+  if (!db_result(db_query('SELECT oid FROM {ad_owners} WHERE aid = %d AND uid = %d', $node->nid, $uid))) {
+    db_query('INSERT INTO {ad_owners} (aid, uid) VALUES(%d, %d)', $node->nid, $uid);
+    $rc = db_affected_rows() ? 1 : 0;
+
+    if (!$permissions) {
+      $permissions = variable_get('ad_'. $node->adtype .'_default_permissions', array('access statistics', 'access click history', 'manage status'));
+    }
+
+    $oid = db_result(db_query("SELECT oid FROM {ad_owners} WHERE aid = %d and uid = %d", $node->nid, $uid));
+    if ($GLOBALS['db_type'] == 'pgsql') {
+      db_query('START TRANSACTION;');
+    }
+    else {
+      // MySQL, MySQLi
+      db_query('LOCK TABLES {ad_permissions} WRITE');
+    }
+    db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $oid);
+    db_query("INSERT INTO {ad_permissions} VALUES(%d, '%s')", $oid, implode('|,|', $permissions));
+    module_invoke_all('adowners', 'add', $node, array('oid' => $oid, 'uid' => $uid, 'aid' => $node->nid));
+  }
+  if ($GLOBALS['db_type'] == 'pgsql') {
+    db_query('COMMIT;');
+  }
+  else {
+    // MySQL, MySQLi
+    db_query('UNLOCK TABLES');
+  }
+  return $rc;
+}
+
+/**
+ * Create a unique host id for each ad owner, used when displaying ads remotely.
+ */
+function ad_host_id_create($uid) {
+  $hostid = db_result(db_query('SELECT hostid FROM {ad_hosts} WHERE uid = %d', $uid));
+  if (!$hostid) {
+    $hostid = md5($uid . time());
+    db_query("INSERT INTO {ad_hosts} (uid, hostid) VALUES (%d, '%s')", $uid, md5($uid . time()));
+  }
+
+  return $hostid;
+}
+
+/**
+ * Removes ad owner from an ad.
+ */
+function ad_owner_remove_form($form_state, $node, $owner) {
+  $form['aid'] = array(
+    '#type' => 'value',
+    '#value' => $node->nid,
+  );
+  $form['uid'] = array(
+    '#type' => 'value',
+    '#value' => $owner->uid,
+  );
+
+  return confirm_form($form,
+    t('Are you sure you want to remove user %name as an owner of this advertisement?', array('%name' => $owner->name)),
+    "node/$aid/adowners",
+    t('This action cannot be undone.'),
+    t('Remove'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Don't allow the removal of the primary owner of the advertisement.
+ */
+function ad_owner_remove_form_validate($form, &$form_state) {
+  $node = node_load($form_state['values']['aid']);
+  if ($node->uid == $form_state['values']['uid']) {
+    $owner = user_load(array('uid' => $form_state['values']['uid']));
+    drupal_set_message(t('%name is the primary owner of this advertisement.  You cannot remove the primary owner.', array('%name' => $owner->name)), 'error');
+
+    $form_state['redirect'] = 'node/'. $form_state['values']['aid'] .'/adowners';
+  }
+}
+
+/**
+ * Remove the ad owner, and all associated permissions.
+ */
+function ad_owner_remove_form_submit($form, &$form_state) {
+  $oid = db_result(db_query('SELECT oid FROM {ad_owners} WHERE aid = %d AND uid = %d', $form_state['values']['aid'], $form_state['values']['uid']));
+  db_query('DELETE FROM {ad_owners} WHERE oid = %d', $oid);
+  db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $oid);
+  $owner = user_load(array('uid' => $form_state['values']['uid']));
+  module_invoke_all('adowners', 'remove', $oid, $owner);
+  drupal_set_message(t('The ad owner %name has been removed.', array('%name' => $owner->name)));
+
+  $form_state['redirect'] = 'node/'. $form_state['values']['aid'] .'/adowners';
+}
+
+
+/**
+ * Display a form with all available permissions and their status for the
+ * selected ad and ad owner.
+ */
+function ad_owner_permissions_form($form_state, $node, $user) {
+  drupal_set_title(t('Permissions'));
+
+  $oid = db_result(db_query("SELECT oid FROM {ad_owners} WHERE aid = %d and uid = %d", $node->nid, $user->uid));
+  $granted = explode('|,|', db_result(db_query("SELECT permissions FROM {ad_permissions} WHERE oid = %d", $oid)));
+
+  $form['header'] = array(
+    '#type' => 'value',
+    '#value' => array(t('permissions'), t('granted'))
+  );
+
+  $rows = array();
+
+  $permissions = module_invoke_all('adapi', 'permissions', $node);
+  foreach ($permissions as $permission) {
+    $form['permission'][$permission] = array(
+      '#value' => t($permission),
+    );
+    $form['grant'][str_replace(' ', '_', $permission)] = array(
+      '#type' => 'checkbox',
+      '#default_value' => in_array($permission, $granted) ? 1 : 0,
+    );
+  }
+
+  $form['oid'] = array(
+    '#type' => 'hidden',
+    '#value' => $oid,
+  );
+
+  $form['aid'] = array(
+    '#type' => 'hidden',
+    '#value' => $node->nid,
+  );
+
+  $form['uid'] = array(
+    '#type' => 'hidden',
+    '#value' => $user->uid,
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+/**
+ * Display ad owner permissions in a simple table.
+ */
+function theme_ad_owner_permissions_form($form) {
+  $output = drupal_render($form['options']);
+  foreach (element_children($form['permission']) as $key) {
+    $row = array();
+    $row[] = drupal_render($form['permission'][$key]);
+    $row[] = drupal_render($form['grant'][str_replace(' ', '_', $key)]);
+    $rows[] = $row;
+  }
+
+  $output = theme('table', $form['header']['#value'], $rows);
+  $output .= drupal_render($form);
+  return $output;
+}
+
+/**
+ * Store the ad owner's updated permissions in the ad_permissions table.
+ */
+function ad_owner_permissions_form_submit($form, &$form_state) {
+  $permissions = module_invoke_all('adapi', 'permissions', array());
+  $perms = array();
+  foreach ($permissions as $permission) {
+    if ($form_state['values'][str_replace(' ', '_', $permission)]) {
+      $perms[] = $permission;
+    }
+  }
+  if ($GLOBALS['db_type'] == 'pgsql') {
+    db_query('START TRANSACTION;');
+  }
+  else {
+    // MySQL, MySQLi
+    db_query('LOCK TABLES {ad_permissions} WRITE');
+  }
+  db_query('DELETE FROM {ad_permissions} WHERE oid = %d', $form_state['values']['oid']);
+  db_query("INSERT INTO {ad_permissions} VALUES(%d, '%s')", $form_state['values']['oid'], implode('|,|', $perms));
+  if ($GLOBALS['db_type'] == 'pgsql') {
+    db_query('COMMIT;');
+  }
+  else {
+    // MySQL, MySQLi
+    db_query('UNLOCK TABLES');
+  }
+  drupal_set_message(t('The permissions have been saved.'));
+  return 'node/'. $form_state['values']['aid'] .'/adowners';
+}


