You are here

ad.module in Advertisement 5.2

An advertising system for Drupal powered websites.

Copyright (c) 2005-2008. Jeremy Andrews <jeremy@kerneltrap.org>. All rights reserved.

File

ad.module
View source
<?php

/**
 * @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.
 *
 * @param $group
 *  The ad group tid to display ads from.
 * @param $quantity
 *  Optionally specify the number of unique ads to display.
 * @param $options
 *  Any number of options from this list:  hostid, nids.
 */
function ad($group = FALSE, $quantity = 1, $options = array()) {
  global $base_url;
  $adserve = variable_get('adserve', '');
  $adserveinc = variable_get('adserveinc', '');
  if (empty($adserve) || empty($adserveinc)) {

    // This is probably the first time ad() has been called.
    _ad_check_install();
    $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();
    return t('The ad module is <a href="@misconfigured">misconfigured</a>.', array(
      '@misconfigured' => url('admin/content/ad/configure'),
    ));
  }

  // Be sure a display method has been chosen.
  if (!isset($options['ad_display'])) {
    $options['ad_display'] = variable_get('ad_display', 'javascript');
  }
  $options['quantity'] = isset($quantity) ? $quantity : 1;
  if (!isset($options['tids'])) {
    $options['tids'] = $group;
  }
  $options['cache'] = variable_get('ad_cache', 'none');
  switch ($options['ad_display']) {
    case 'raw':
      require_once drupal_get_path('module', 'ad') . '/adserve.inc';
      require_once drupal_get_path('module', 'ad') . '/adcache.inc';
      $output = adserve_ad($options);
      break;
    case 'iframe':
    case 'jquery':
      $display_variables = 'm=' . $options['ad_display'];

    // Fall through...
    case 'javascript':
    default:
      if ($display_variables) {
        $display_variables .= "&amp;q={$quantity}";
      }
      else {
        $display_variables = "q={$quantity}";
      }
      if ($hostid = $options['hostid']) {
        $display_variables .= "&amp;k={$hostid}";
      }

      // Allow external cache files to define additional display variables.
      if ($options['cache'] != 'none') {
        $display_variables .= '&amp;c=' . $options['cache'];
        $cache_variables = module_invoke('ad_cache_' . $options['cache'], 'adcacheapi', 'display_variables', array());
        if (is_array($cache_variables)) {
          foreach ($cache_variables as $key => $value) {
            $display_variables .= "&amp;{$key}={$value}";
          }
        }
      }

      // Allow ad_type modules to define additional display variables.
      $type_variables = module_invoke_all('adapi', 'display_variables', array());
      if (is_array($type_variables)) {
        foreach ($type_variables as $key => $value) {
          $display_variables .= "&amp;{$key}={$value}";
        }
      }
      if ($nids = $options['nids']) {

        // Choose ads from the provided list of node Id's.
        $display_variables .= "&amp;n={$nids}";
        $group = "nids-{$nids}";
      }
      else {
        if ($tids = $options['tids']) {

          // Choose ads from the provided list of taxonomy terms.
          $display_variables .= "&amp;t={$tids}";
          $group = "tids-{$tids}";
        }
        else {

          // Choose ads from the specified group.
          $display_variables .= "&amp;t={$group}";
          $options['tids'] = $group;
        }
      }
      if (isset($options['url'])) {
        $display_variables .= '&amp;u=' . $options['url'];
      }
      else {
        $display_variables .= '&amp;u=' . $_GET['q'];
      }
      $src = url("{$base_url}/{$adserve}?{$display_variables}");
      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.
        // For now we'll have a global value, later we'll add per-group
        // over-rides.
        $append = 'frameborder="' . variable_get('ad_iframe_frameborder', 0) . '" ';
        $append .= 'scrolling="' . variable_get('ad_iframe_scroll', 'auto') . '" ';
        $append .= "name=\"{$group}\" ";
        if ($height = variable_get('ad_iframe_height', '')) {
          $append .= "height=\"{$height}\" ";
        }
        if ($width = variable_get('ad_iframe_width', '')) {
          $append .= "width=\"{$width}\" ";
        }
        $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>";
        }
      }
      break;
  }
  if (user_access('show advertisements')) {
    if ($options['div'] !== FALSE) {
      return theme('ad_display', $group, $output, $options['ad_display']);
    }
    else {
      return theme('ad_display', $group, $output, 'raw');
    }
  }
  else {
    return theme('ad_display', 'none', "<!-- Enable 'show advertisements' permission if you wish to display ads here. -->");
  }
}

/**
 * Function to display the actual advertisement to the screen.  Wrap it in a 
 * theme function to make it possible to customize in your own theme.
 */
function theme_ad_display($group, $display, $method = 'javascript') {
  static $id = 0;

  // The naming convention for the id attribute doesn't allow commas.
  $group = preg_replace('/[,]/', '+', $group);
  if ($method == 'jquery') {
    _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 {
    if ($method == 'raw') {
      return $display;
    }
    else {
      return "\n<div class=\"advertisement group-{$group}\" id=\"group-id-{$group}\">{$display}</div>\n";
    }
  }
}

/**
 * Update click counter then redirect host to ad's target URL.
 */
function ad_redirect($aid, $group = NULL, $extra = NULL, $hostid = NULL) {
  global $user;
  if (function_exists('click_filter_status')) {
    $status = click_filter_status($aid, $hostid);
    if ($status == CLICK_VALID) {
      ad_statistics_increment($aid, 'click', $group, $hostid);
    }
  }
  else {

    // We're not filtering clicks, so all clicks are valid.
    ad_statistics_increment($aid, 'click', $group, $hostid);
    $status = 0;
  }

  // Allow source url to be passed in.
  if (!($url = $_GET['u']) || !valid_url($url)) {
    $url = referer_uri();
  }
  db_query("INSERT INTO {ad_clicks} (aid, uid, status, hostname, user_agent, adgroup, extra, hostid, url, timestamp) VALUES (%d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d)", $aid, $user->uid, $status, $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT'], $group, $extra, $hostid, $url, time());

  // Determine where we're supposed to redirect the user.
  $adtype = db_result(db_query('SELECT adtype FROM {ads} WHERE aid = %d', $aid));
  $node->nid = $node->aid = $aid;
  $node->hostid = $hostid;
  $url = module_invoke('ad_' . $adtype, 'adapi', 'redirect', $node);
  if (isset($url)) {
    watchdog('ad', t('Clicked %type ad aid %aid hostid %hostid.', array(
      '%type' => $adtype,
      '%aid' => $aid,
      '%hostid' => $hostid,
    )));
    header('Location: ' . $url);
  }
  else {
    watchdog('ad', t('Ad redirection failed for aid %aid hostid %hostid, failed to load destination URL. ', array(
      '%aid' => $aid,
      '%hostid' => $hostid,
    )));
    drupal_goto('');
  }
}

/**
 * Ad API Helper Function:
 * Append all necessary attributes to <a> tags.
 */
function ad_link_attributes() {
  $output = ad_link_target();
  $output .= ad_link_nofollow();
  return $output;
}

/**
 * Ad API Helper Function:
 * Provide XHTML-strict-compatible target window onclick-handlers based on
 * global configuration.
 */
function ad_link_target() {
  switch (variable_get('ad_link_target', '_self')) {
    case '_blank':
      $target = ' onclick="window.open(this.href); return false;"';
      break;
    case '_parent':
      $target = ' onclick="window.parent.location = this.href; return false;"';
      break;
    case '_top':
      $target = ' onclick="window.top.location = this.href; return false;"';
      break;
    default:
      $target = '';
      break;
  }
  return $target;
}

/**
 * Force the cache to be flushed.
 */
function ad_rebuild_cache($verbose = FALSE) {
  $cache = variable_get('ad_cache', 'none');
  $build = "ad_cache_{$cache}_build";
  if (function_exists($build)) {
    if ($verbose) {
      drupal_set_message('Rebuilding ad cache.');
    }
    $build();
  }
}

/**
 * Ad API Helper Function:
 * Append rel="nofollow" if globally enabled.
 */
function ad_link_nofollow() {
  if (variable_get('ad_link_nofollow', 0)) {
    return ' rel="nofollow"';
  }
  return;
}

/**
 * Increment action counter.
 */
function ad_statistics_increment($aid, $action, $group = NULL, $hostid = NULL) {

  // Update action statistics.
  db_query("UPDATE {ad_statistics} SET count = count + 1 WHERE date = %d AND aid = %d AND action = '%s' AND adgroup = '%s' AND hostid = '%s'", date('YmdH'), $aid, $action, $group, $hostid);

  // If column doesn't already exist, we need to add it.
  if (!db_affected_rows()) {
    db_query("INSERT INTO {ad_statistics} (aid, adgroup, hostid, date, action, count) VALUES(%d, '%s', '%s', %d, '%s', 1)", $aid, $group, $hostid, date('YmdH'), $action);

    // If another process already added this row our INSERT will fail, if so we
    // still need to increment it so we don't loose an action.
    if (!db_affected_rows()) {
      db_query("UPDATE {ad_statistics} SET count = count + 1 WHERE date = %d AND aid = %d AND action = '%s' AND adgroup = '%s' AND hostid = '%s'", date('YmdH'), $aid, $action, $group, $hostid);
    }
  }
  $event = array(
    'aid' => $aid,
    'action' => $action,
    'hostid' => $hostid,
  );
  module_invoke_all('adapi', 'statistics_increment', $event);
}

/**
 * Return an array with all status values user has permission to set.
 * A user with 'administer advertisements' permission can update any status.
 */
function ad_status_array($aid = 0, $status = NULL) {
  if (!$aid && !$status) {
    $all = TRUE;
  }
  else {
    $all = FALSE;
  }
  $permissions = array();

  // mark status as pending
  if ($all || user_access('administer advertisements') || $status == 'pending' || ad_permission($aid, 'set status as pending')) {
    $permissions['pending'] = t('This advertisement is currently waiting for administrative approval.');
  }

  // mark status from pending to approved
  if ($all || user_access('administer advertisements') || $status == 'approved' || $status == 'pending' && ad_permission($aid, 'set status from pending to approved')) {
    $permissions['approved'] = t('This advertisement has been approved and is currently waiting to be activated.');
  }

  // mark status as active (from pending, approved, or offline)
  if ($all || user_access('administer advertisements') || $status == 'active' || $status == 'approved' && ad_permission($aid, 'set status from approved to active') || $status == 'offline' && ad_permission($aid, 'set status from offline to active')) {
    $permissions['active'] = t('This advertisement is actively being displayed.');
  }

  // mark status as offline (from pending, approved, or active)
  if ($all || user_access('administer advertisements') || $status == 'offline' || $status == 'approved' && ad_permission($aid, 'set status from approved to offline') || $status == 'active' && ad_permission($aid, 'set status from active to offline')) {
    $permissions['offline'] = t('This advertisement has been temporarily disabled by its owner and is not currently being displayed.');
  }

  // mark status as expired (from active or offline)
  if ($all || user_access('administer advertisements') || $status == 'expired' || $status == 'active' && ad_permission($aid, 'set status from active to expired') || $status == 'offline' && ad_permission($aid, 'set status from offline to expired')) {
    $permissions['expired'] = t('This advertisement has expired and is no longer being displayed.');
  }

  // mark status as denied (from pending or any)
  if ($all || user_access('administer advertisements') || $status == 'denied' || $status == 'pending' && ad_permission($aid, 'set status from pending to denied') || ad_permission($aid, 'set status as denied')) {
    $permissions['denied'] = t('This advertisement was refused by the site administrator and will not be displayed.');
  }
  return $permissions;
}

/**
 * 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) {
  $status_array = ad_status_array();
  $output = '<div class="adstatus">';
  $output .= '<p>' . t($status_array["{$node->adstatus}"]) . '</p>';
  switch ($node->adstatus) {
    case 'approved':
      if ($node->autoactivate) {
        $output .= '<p>' . t('This advertisement will be automatically activated on %timestamp, in %time.', array(
          '%timestamp' => format_date($node->autoactivate, 'large'),
          '%time' => format_interval($node->autoactivate - time()),
        )) . '</p>';
      }
      break;
    case 'active':
      $activated = db_result(db_query("SELECT activated FROM {ads} WHERE aid = %d", $node->nid));
      if ($activated) {
        $output .= '<p>' . t('This advertisement has been active since %date.', array(
          '%date' => format_date($activated, 'large'),
        )) . '</p>';
      }
      if ($node->autoexpire) {
        $output .= '<p>' . t('This advertisement will expire on %timestamp, in %time.', array(
          '%timestamp' => format_date($node->autoexpire, 'large'),
          '%time' => format_interval($node->autoexpire - time()),
        )) . '</p>';
      }
      if ($node->maxviews) {
        $views = (int) db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $node->nid, date('YmdH', $node->activated)));
        $output .= '<p>' . t('This advertisement will expire after %left more views.', array(
          '%left' => $node->maxviews - $views,
        )) . '</p>';
      }
      if ($node->maxclicks) {
        $clicks = (int) db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $node->nid, date('YmdH', $node->activated)));
        $output .= '<p>' . t('This advertisement will expire after %left more clicks.', array(
          '%left' => $node->maxclicks - $clicks,
        )) . '</p>';
      }
      break;
    case 'expired':
      $expired = db_result(db_query("SELECT expired FROM {ads} WHERE aid = %d", $node->nid));
      if ($expired) {
        $output .= '<p>' . t('This advertisement expired %date.', array(
          '%date' => format_date($expired, 'large'),
        )) . '</p>';
      }
      break;
  }
  $output .= '</div>';
  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().
 */
function ad_help($path) {
  switch ($path) {
    case 'admin/help#ad':
      $output = '<p>' . t('The ad module provides a complete advertising system for Drupal powered websites.  It does this through an API that allow other modules to handle various types of advertising content.  For example, if enabled together with the ad_image module you will be able to display image based advertisements such as banner ads.') . '</p>';
      break;
  }
  return $output;
}

/**
 * Drupal _cron hook.
 */
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');
    while ($ad = db_fetch_object($result)) {
      switch ($ad->adstatus) {
        case 'approved':

          // See if this ad is ready to be activated.
          if ($ad->autoactivate && $ad->autoactivate <= time()) {
            $node = node_load($ad->aid);

            // Activate the ad.
            db_query("UPDATE {ads} SET adstatus = 'active', autoactivate = 0, autoactivated = %d, activated = %d WHERE aid = %d", time(), time(), $ad->aid);
            ad_statistics_increment($ad->aid, 'autoactivated');
            ad_statistics_increment($ad->aid, 'active');
            watchdog('ad', t('Automatically activated ad %title with nid %nid.', array(
              '%title' => $node->title,
              '%nid' => $node->nid,
            )));

            // Allow modules to do special processing to automatically
            // activated advertisements.
            module_invoke('ad_' . $ad->adtype, 'adapi', 'autoactivate', $node);
          }
          else {
            if (!$ad->autoactivate) {

              // Once daily warn that there's an ad stuck in approved state.
              if (time() - variable_get("ad_autoactivate_warning_{$ad->aid}", 0) >= 8600) {
                watchdog('ad', t('Warning: ad %title with nid %nid in approved state has no autoactivate date set.', array(
                  '%title' => $node->title,
                  '%nid' => $node->nid,
                )));
                variable_set("ad_autoactivate_warning_{$ad->aid}", time());
              }
            }
          }
          break;
        case 'active':

          // See if this ad is ready to be activated.
          if ($ad->autoexpire && $ad->autoexpire <= time()) {
            $node = node_load($ad->aid);

            // Expire the ad.
            db_query("UPDATE {ads} SET adstatus = 'expired', autoexpire = 0, autoexpired = %d, expired = %d WHERE aid = %d", time(), time(), $ad->aid);
            ad_statistics_increment($ad->aid, 'autoexpired');
            ad_statistics_increment($ad->aid, 'expired');
            watchdog('ad', t('Automatically expired ad %title with nid %nid.', array(
              '%title' => $node->title,
              '%nid' => $node->nid,
            )));

            // Allow modules to do special processing to automatically
            // activated advertisements.
            module_invoke('ad_' . $ad->adtype, 'adapi', 'autoexpire', $node);
          }
          else {
            if (!$ad->autoexpire) {

              // Ad is already activated, but has autoactivate timestamp set.
              db_query("UPDATE {ads} SET autoactivate = 0 WHERE aid = %d", $ad->aid);
            }
          }
          break;
        default:
          $node = node_load($ad->aid);
          db_query('UPDATE {ads} SET autoactivate = 0, autoexpire = 0 WHERE aid = %d', $ad->aid);
          watchdog('ad', t('Warning: reset %type timestamp on advertisement %title with nid %nid because it is in %state state.', array(
            '%title' => $node->title,
            '%nid' => $node->nid,
            '%type' => $ad->autoactivate ? 'autoactivate' : 'autoexpire',
            '%state' => $ad->adstatus,
          )));
      }
    }
    variable_set('ad_cron_timestamp', time());
  }
}

/**
 * Drupal _perm hook.  Establishes permissions used by this module.
 *
 * @return  An array of permissions used by this module.
 */
function ad_perm() {
  return array(
    'administer advertisements',
    'create advertisements',
    'edit own advertisements',
    'show advertisements',
  );
}

/**
 */
function ad_node_info() {
  return array(
    '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.'),
    ),
  );
}

/**
 */
function ad_access($op, $node) {
  global $user;
  if ($op == 'create') {
    return user_access('create advertisements');
  }
  if ($op == 'update' || $op == 'delete') {
    return user_access('administer advertisements') || ad_permission_is_owner($node->nid) && user_access('edit own advertisements');
  }
}

/**
 * Drupal _form hook.
 */
function ad_form(&$node) {
  $form = array();
  $edit = $_POST['edit'];
  $type = arg(3);
  if (function_exists("ad_{$type}" . '_type')) {
    $adtype = $type;
  }
  else {
    $adtype = $node->adtype;
  }
  $form['aid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#required' => TRUE,
    '#default_value' => $node->title,
  );
  $form['body_filter']['body'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => $node->body,
    '#rows' => 3,
  );
  $form['body_filter']['format'] = filter_form($node->format);

  // determine the current ad type
  if (!isset($adtype)) {
    $adtypes = module_invoke_all('adapi', 'type', array());
    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/modules'),
        )), 'error');
        break;
      case 1:
        $adtype = $adtypes[0];
        break;
      default:
        if (arg(0) == 'node' && arg(1) == 'add' && arg(2) == 'ad') {
          $adtype = arg(3) ? arg(3) : $edit['adtype'];
          $form['adtype'] = array(
            '#type' => 'radios',
            '#title' => t('Style of ad'),
            '#options' => drupal_map_assoc($adtypes),
            '#default_value' => $adtype ? $adtype : $adtypes[0],
            '#required' => TRUE,
            '#description' => t('Select the type of ad that you wish to create from the above options.'),
          );
        }
        break;
    }
  }

  // display type-specific options
  if (isset($adtype)) {
    $elements = module_invoke('ad_' . $adtype, 'adapi', 'form', $node);
    foreach ($elements as $element => $values) {
      $form["{$element}"] = $values;
    }
    $form['adtype'] = array(
      '#type' => 'hidden',
      '#value' => $adtype,
    );
  }

  // fieldset for updating ad status
  $form['adstatus'] = array(
    '#type' => 'fieldset',
    '#title' => t('Status'),
    '#collapsible' => TRUE,
  );

  // display all available status options
  foreach (ad_status_array($node->nid, $node->adstatus) as $status => $description) {
    $form['adstatus']["ad{$status}"] = array(
      '#type' => 'radio',
      '#title' => t("{$status}"),
      '#return_value' => $status,
      '#default_value' => $node->adstatus ? $node->adstatus : 'pending',
      '#description' => "{$description}",
      '#parents' => array(
        "adstatus",
      ),
    );
  }
  if (ad_permission($node->nid, 'access statistics')) {

    // display statistics
    $form['statistics'] = array(
      '#type' => 'fieldset',
      '#title' => t('Statistics'),
      '#collapsible' => TRUE,
    );
    $form['statistics']['data'] = array(
      '#type' => 'markup',
      '#prefix' => '<div>',
      '#suffix' => '</div>',
      '#value' => theme('ad_statistics_display', ad_statistics($node->nid)),
    );
  }

  // display scheduling options
  $form['schedule'] = array(
    '#type' => 'fieldset',
    '#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'] ? FALSE : TRUE,
  );
  if (ad_permission($node->nid, 'manage status')) {
    $form['schedule']['current'] = array(
      '#type' => 'markup',
      '#prefix' => '<div>',
      '#suffix' => '</div>',
      '#value' => t('The current date and time is "%date".', array(
        '%date' => format_date(time(), 'custom', 'F j, Y H:i'),
      )),
    );
    $form['schedule']['autoactivate'] = array(
      '#type' => 'textfield',
      '#title' => t('Automatically activate ad'),
      '#required' => FALSE,
      '#default_value' => $node->autoactivate ? format_date((int) $node->autoactivate, 'custom', 'F j, Y H:i') : '',
      '#description' => t('You can specify a date and time for this advertisement to be automatically activated.  The advertisement needs to be in an <em>approved</em> state before it can be automatically activated.  If you prefer to activate the advertisement immediately, leave this field empty.'),
    );
  }
  if (user_access('administer advertisements')) {

    // admins can expire advertisements
    $form['schedule']['autoexpire'] = array(
      '#type' => 'textfield',
      '#title' => t('Automatically expire ad'),
      '#required' => FALSE,
      '#default_value' => $node->autoexpire ? format_date((int) $node->autoexpire, 'custom', 'F j, Y H:i') : '',
      '#description' => t('You can specify a date and time for this advertisement to be automatically expired.  If you don\'t want the advertisement to expire, leave this field empty.'),
    );
    $form['schedule']['maxviews'] = array(
      '#type' => 'textfield',
      '#title' => t('Maximum views'),
      '#required' => FALSE,
      '#size' => 10,
      '#maxlength' => 11,
      '#default_value' => $node->maxviews,
      '#description' => t('You can specify the maximum number of times this advertisement should be displayed, after which it will be automatically expired.  If you don\'t want this advertisement to expire after a certain number of views, leave this field set to %zero.', array(
        '%zero' => '0',
      )),
    );
    $form['schedule']['maxclicks'] = array(
      '#type' => 'textfield',
      '#title' => t('Maximum clicks'),
      '#required' => FALSE,
      '#size' => 10,
      '#maxlength' => 11,
      '#default_value' => $node->maxclicks,
      '#description' => t('You can specify the maximum number of times this advertisement should be clicked, after which it will be automatically expired.  If you don\'t want this advertisement to expire after a certain number of clicks leave this field set to %zero.', array(
        '%zero' => '0',
      )),
    );
  }
  else {

    // display expiration time
    $form['schedule']['autoexpire_display'] = array(
      '#type' => 'markup',
      '#prefix' => '<div>',
      '#suffix' => '</div>',
      '#value' => theme('ad_status_display', $node),
    );
    $form['schedule']['autoexpire'] = array(
      '#type' => 'hidden',
      '#value' => $node->autoexpire,
    );
  }
  return $form;
}

/**
 * Drupal _form_alter() hook.
 */
function ad_form_alter($form_id, &$form) {
  if ($form_id == 'taxonomy_form_vocabulary') {

    // Remove taxonomy form options not applicable for ad groups.
    if ($form['vid']['#value'] == _ad_get_vid()) {
      $form['help_ad_vocab'] = array(
        '#value' => t('This vocabulary was automatically created for use by the ad module.  Only applicable options are available.'),
        '#weight' => -1,
      );
      $form['nodes']['ad'] = array(
        '#type' => 'checkbox',
        '#title' => t('ad group'),
        '#value' => 1,
        '#attributes' => array(
          'disabled' => '',
        ),
        '#description' => t('Type %type is required to use this vocabulary.', array(
          '%type' => t('ad group'),
        )),
      );
      $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,
      );
      unset($form['relations']);
    }
    else {
      unset($form['nodes']['ad']);
    }
  }
  else {
    if ($form_id == 'taxonomy_form_term') {
      if ($form['vid']['#value'] == _ad_get_vid()) {
        $form['name']['#title'] = t('Ad group name');
        $form['name']['#description'] = t('The name for this ad group.  Example: "Linux".');
        $form['description']['#description'] = t('A description of the ad group.');
        $form['description']['#required'] = TRUE;
        $form['weight']['#description'] = t('In listings, the heavier ad groups will sink and the lighter ad groups will be positioned nearer the top.');
        unset($form['synonyms']);
      }
    }
  }
}

/**
 * Drupal _nodeapi hook.
 */
function ad_nodeapi(&$node, $op, $teaser, $page) {
  global $user;
  switch ($op) {
    case 'load':
      $ad = db_fetch_array(db_query('SELECT * FROM {ads} WHERE aid = %d', $node->nid));
      $adtype = module_invoke('ad_' . $ad['adtype'], 'adapi', 'load', $ad);
      if (is_array($adtype)) {
        return array_merge($ad, $adtype);
      }
      else {
        return $ad;
      }
      break;
    case 'insert':
      if ($node->adtype) {
        if ($node->status != 1 && $node->adstatus == 'active') {
          $node->adstatus = 'expired';
        }
        $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}", NULL, NULL, 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;
    case 'update':
      if ($node->adtype) {
        $ad = db_fetch_object(db_query('SELECT * FROM {ads} WHERE aid = %d', $node->nid));

        // Ad must be in approved state to be able to autoactivate it.
        if ($node->adstatus != 'approved' && $node->autoactivate) {
          if ($node->adstatus == 'active') {

            // This ad is already active, no need to autoactivate it.
            $node->autoactivate = 0;
          }
          else {
            drupal_set_message(t('This ad will not be automatically activated at the scheduled time because it is not in the <em>approved</em> state.'), 'error');
          }
        }

        // If this node has been upublished, the ad should no longer be active.
        if ($node->status != 1 && $node->adstatus == 'active') {
          $node->adstatus = 'expired';
        }

        // Check if ad is being manually activated.
        if ($ad->adstatus != 'active' && $node->adstatus == 'active') {
          $activated = time();
        }
        else {
          if ($ad->status != 'expired' && $node->adstatus == 'expired') {

            // Ad has been manually expired.
            $expired = time();
          }
          else {
            $activated = $ad->activated;
            $expired = $ad->expired;
          }
        }

        // Ad status has changed, record the event.
        if ($ad->adstatus != $node->adstatus) {
          ad_statistics_increment($node->nid, $node->adstatus);
        }

        // Update ads table with new information.
        db_query("UPDATE {ads} SET uid = %d, adstatus = '%s', adtype = '%s', autoactivate = %d, autoexpire = %d, activated = %d, maxviews = %d, maxclicks = %d, expired = %d WHERE aid = %d", $node->uid, $node->adstatus, $node->adtype, $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_statistics_increment($node->nid, 'update');
      }
      break;
    case 'delete':
      db_query("DELETE FROM {ads} WHERE aid = %d", $node->nid);
      db_query("DELETE FROM {ad_statistics} 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');
      break;
    case 'view':
      if ($node->adtype) {
        $node = node_prepare($node, $teaser);
        $node->content['body'] = array(
          '#value' => $teaser ? $node->teaser : theme('node_ad', $node, $page),
          '#weight' => 1,
        );
      }
      break;
  }

  // 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($op, $node);
    }
  }

  // Rebuild the cache after all hooks are invoked.
  switch ($op) {
    case 'insert':
    case 'update':
    case 'delete':
      if ($node->adtype) {
        ad_rebuild_cache();
      }
  }
}
function theme_node_ad($node, $yield_form = TRUE) {
  $output = '';
  if (ad_permission($node->nid, 'access statistics')) {
    $output = theme('ad_status_display', $node);
    $output .= theme('ad_statistics_display', ad_statistics($node->nid));
  }
  $output .= ad_click_history($node->nid);
  if (variable_get('ad_filter', 0)) {
    $output = check_markup($output, $node->format);
  }
  return $output;
}

/**
 * Display click history for advertisemet, if user has necessary permission.
 */
function ad_click_history($aid) {
  $output = '';
  if (ad_permission($aid, '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 ($aid) {
      $sql = "SELECT cid, timestamp, uid, status, hostname, url FROM {ad_clicks} WHERE aid = {$aid}";
      $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('details', "node/{$aid}/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);
    }
  }
  return $output;
}

/**
 * Display details about a specific click.
 */
function ad_click_details($aid, $cid) {
  drupal_set_title(t('Click details'));
  $node = node_load($aid);
  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' => TRUE,
        'access click history' => TRUE,
        'set status as pending' => FALSE,
        'set status as denied' => FALSE,
        'set status from pending to approved' => FALSE,
        'set status from pending to denied' => FALSE,
        'set status from approved to active' => TRUE,
        'set status from approved to offline' => TRUE,
        'set status from active to offline' => TRUE,
        'set status from active to expired' => FALSE,
        'set status from offline to active' => TRUE,
        'set status from offline to expired' => FALSE,
      );
  }
}

/**
 * Implementation of hook_menu().
 */
function ad_menu($may_cache) {
  global $user;
  $items = array();
  if ($may_cache) {

    // menu items
    $items[] = array(
      'path' => 'admin/content/ad',
      'title' => t('Ads'),
      'callback' => 'ad_admin_list',
      'access' => user_access('administer advertisements'),
      'description' => t('Configure and manage your advertising system.'),
    );

    // tabs
    $items[] = array(
      'path' => 'admin/content/ad/list',
      'title' => t('List'),
      'callback' => 'ad_admin_list',
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/content/ad/configure',
      'title' => t('Settings'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'ad_admin_configure_settings',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => 3,
    );
    $items[] = array(
      'path' => 'admin/content/ad/groups',
      'title' => t('Groups'),
      'callback' => 'ad_admin_groups_list',
      'type' => MENU_LOCAL_TASK,
      'weight' => 5,
    );

    // groups sub tabs
    $items[] = array(
      'path' => 'admin/content/ad/groups/list',
      'title' => t('List'),
      'callback' => 'ad_admin_groups_list',
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => 0,
    );
    $items[] = array(
      'path' => 'admin/content/ad/groups/add',
      'title' => t('Create group'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'ad_admin_group_form',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => 3,
    );

    // configure sub tabs
    $items[] = array(
      'path' => 'admin/content/ad/configure/global',
      'title' => t('Global settings'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'ad_admin_configure_settings',
      ),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => 0,
    );
    $items[] = array(
      'path' => 'node/add/ad',
      'title' => t('Ad'),
      'callback' => 'ad_add',
      'access' => user_access('create advertisements'),
    );
    $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[] = array(
        'path' => 'admin/content/ad/configure/' . $adtype,
        'title' => t('!adtype ads', array(
          '!adtype' => drupal_ucfirst($adtype),
        )),
        'callback' => 'drupal_get_form',
        'callback arguments' => array(
          $settings,
        ),
        'type' => MENU_LOCAL_TASK,
        'weight' => 2,
      );

      // Node add items.
      $items[] = array(
        'path' => 'node/add/ad/' . $adtype,
        'title' => t('!type advertisement', array(
          '!type' => t($adtype),
        )),
        'access' => user_access('create advertisements'),
      );
    }
  }
  else {

    // callbacks
    if (arg(0) == 'ad' && arg(1) == 'redirect' && is_numeric(arg(2))) {
      $aid = preg_replace('/[^0-9]/', '', arg(2));
      $group = preg_replace('/[^0-9,nt]/', '', arg(3));
      $extra = preg_replace('/[^0-9,:a-zA-Z]/', '', arg(4));
      $hostid = arg(5);
      if (!empty($hostid)) {
        $hostid = explode('?', $hostid);
        $hostid = preg_replace('/[^0-9a-f]/', '', $hostid[0]);
      }
      $items[] = array(
        'path' => "ad/redirect/{$aid}",
        'access' => user_access('show advertisements'),
        'type' => MENU_CALLBACK,
        'callback' => 'ad_redirect',
        'callback arguments' => array(
          $aid,
          $group,
          $extra,
          $hostid,
        ),
      );
    }
    elseif (arg(2) == 'ad' && arg(3) == 'groups' && is_numeric(arg(4))) {
      if ($term = taxonomy_get_term(arg(4))) {
        $items[] = array(
          'path' => "admin/content/ad/groups/{$term->tid}/edit",
          'title' => t('edit'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'ad_admin_group_form',
            (array) $term,
          ),
          'access' => user_access('administer advertisements'),
          'type' => MENU_CALLBACK,
          'weight' => 1,
        );
        $items[] = array(
          'path' => "admin/content/ad/groups/{$term->tid}/delete",
          'title' => t('delete'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'ad_confirm_group_delete',
            (array) $term,
          ),
          'access' => user_access('administer advertisements'),
          'type' => MENU_CALLBACK,
          'weight' => 2,
        );
      }
    }
    if (is_numeric($nid = arg(1)) && is_numeric($cid = arg(3))) {
      $items[] = array(
        'path' => "node/{$nid}/details/{$cid}",
        'title' => t('Click details'),
        'callback' => 'ad_click_details',
        'callback arguments' => array(
          $nid,
          $cid,
        ),
        'type' => MENU_CALLBACK,
      );
    }
  }
  return $items;
}

/**
 * Drupal _block hook.
 */
function ad_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks = array();
      $groups = ad_groups_list();
      foreach ($groups as $tid => $name) {
        $blocks[$tid]['info'] = t('ad group: @name', array(
          '@name' => $name,
        ));
      }
      return $blocks;
    case 'configure':
      $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,
        )),
        '#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}"]);
      break;
    case 'view':
      $groups = ad_groups_list();
      $block['content'] = ad($delta, variable_get("ad_block_quantity_{$delta}", 1));
      return $block;
  }
}

/****/

/**
 * Present a list of ad types to choose from.
 */
function ad_add() {
  global $user;
  $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_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>';
  }
  return $output;
}

/**
 * Stub for ad_permission module, used to determine whether the user has a
 * given privilege.  If the ad_permission module is not enabled, all permissions
 * are granted.
 */
function ad_permission($aid, $string, $account = NULL) {
  global $user;
  $access = FALSE;

  // by default, check permission for current user
  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;
  }

  // invoke ad_permission module to determine users access
  if (module_exists('ad_permission') && function_exists('ad_permission_check')) {
    $access = ad_permission_check($aid, $string, $account);
  }
  else {
    if (in_array($string, array(
      'access statistics',
      'access click history',
    ))) {
      $access = TRUE;
    }
  }

  // with no ad_permission module, all other permissions are denied unless user
  // has 'administer advertisements' permission
  return $access;
}

/**
 * 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' => $ad->adtype,
    );
    $form['adstatus'][$ad->aid] = array(
      '#value' => $ad->adstatus,
    );
    $form['operations'][$ad->aid] = array(
      '#value' => l(t('edit'), 'node/' . $ad->aid . '/edit', array(), $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',
      ),
    ),
    '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);
    if (module_exists('ad_permission')) {

      // TODO: Move this into ad_permission module
      ad_permission_owners_add($aid, $node->uid);
      ad_permission_create_hostid($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)) {
        $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_id, $form_values) {
  if ($form_values['confirm']) {
    foreach ($form_values['ads'] as $aid => $value) {
      node_delete($aid);
    }
    drupal_set_message(t('The ads have been deleted.'));
  }
  return '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_id, $form_values) {
  $ads = array_filter($form_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_id, $form_values) {
  $operations = module_invoke_all('ad_operations');
  $operation = $operations[$form_values['operation']];

  // Filter out unchecked nodes
  $ads = array_filter($form_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);
    drupal_set_message(t('The update has been performed.'));
    cache_clear_all();
    ad_rebuild_cache(TRUE);
  }
}

/**
 * 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'),
    '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'),
    '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() {
  $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_id, $form_values) {
  $filters = ad_filters();
  switch ($form_values['op']) {
    case t('Filter'):
    case t('Refine'):
      if (isset($form_values['filter'])) {
        $filter = $form_values['filter'];

        // Flatten the options array to accommodate hierarchical/nested options.
        $flat_options = form_options_flatten($filters[$filter]['options']);
        if (isset($flat_options[$form_values[$filter]])) {
          $_SESSION['ad_overview_filter'][] = array(
            $filter,
            $form_values[$filter],
          );
        }
      }
      break;
    case t('Undo'):
      array_pop($_SESSION['ad_overview_filter']);
      break;
    case t('Reset'):
      $_SESSION['ad_overview_filter'] = array();
      break;
  }
}

/**
 * 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_id, $form_values) {
  if ($form_values['ad_link_target'] == '_self' && $form_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_id, $form_values) {
  variable_set('ad_link_target', $form_values['ad_link_target']);
  variable_set('ad_link_nofollow', $form_values['ad_link_nofollow']);
  variable_set('ad_cache', $form_values['ad_cache']);
  variable_set('ad_display', $form_values['ad_display']);
  variable_set('ad_validate_url', $form_values['ad_validate_url']);
  variable_set('ad_filter', $form_values['ad_filter']);
  variable_set('ad_iframe_frameborder', $form_values['ad_iframe_frameborder']);
  variable_set('ad_iframe_scroll', $form_values['ad_iframe_scroll']);
  variable_set('ad_iframe_width', $form_values['ad_iframe_width']);
  variable_set('ad_iframe_height', $form_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_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_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;
        }
      }

      // 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;
    }

    // Return a specific group object.
    if ((int) $tid) {
      return $groups[$tid];
    }
    else {
      return $groups;
    }
  }
  else {
    if (empty($names)) {
      $tids = taxonomy_get_tree(_ad_get_vid());
      if (is_array($tids)) {
        foreach ($tids as $group) {
          $names[$group->tid] = "{$group->name}";
        }
      }

      // Hard coded "default" group with tid of 0.
      $names[0] = t('default');
    }

    // Return a specific group name.
    if ((int) $tid) {
      return $names[$tid];
    }
    else {
      return $names;
    }
  }
}
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;
}

/**
 * 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.
 */
function ad_adnotifyapi($op, $arg1 = NULL, $arg2 = NULL) {
  switch ($op) {

    // Make the following events available for notification.
    case 'register':
      return array(
        '-expired' => t('Email @when before the advertisement will expire.'),
        'expired' => t('Email @when after the advertisement is expired.'),
        '-active' => t('Email @when before the advertisement will be activated (if scheduled).'),
        'active' => t('Email @when after the advertisement is activated.'),
        'click' => t('Email @when after the advertisement is clicked.'),
        'approved' => t('Email @when after the advertisement is approved.'),
        'denied' => t('Email @when after the advertisement is denied.'),
      );
      break;
    case '-expired':
      $node = node_load($arg1->aid);
      if (isset($node->autoexpire) && $node->autoexpire) {
        if (time() + $arg1->delay >= $node->autoexpire && $arg1->sent + $arg1->delay < $node->autoexpire) {
          return array(
            '-expired' => 1,
          );
        }
      }
      break;
    case '-active':
      $node = node_load($arg1->aid);
      if (isset($node->autoactivate) && $node->autoactivate) {
        if (time() + $arg1->delay >= $node->autoactivate && $arg1->sent + $arg1->delay < $node->autoactivate) {
          return array(
            '-active' => 1,
          );
        }
      }
      break;
    case 'mail_text':
      switch ($arg1) {
        case 'expired':
          return array(
            'subject' => t('[%sitename ad] %event notification'),
            'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification to inform you that your advertisement \"%title\" that was being displayed on the %sitename website has expired.\n\n  Your advertisement was viewed %global_views times and clicked %global_clicks times since it was activated on %activated_large.\n\n  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"),
          );
        case '-expired':
          return array(
            'subject' => t('[%sitename ad] expiration notification'),
            'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification to inform you that your advertisement \"%title\" that is being displayed on the %sitename website will expire on %autoexpire_large.\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 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"),
          );
        case 'active':
          return array(
            'subject' => t('[%sitename ad] %event notification'),
            'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification to inform you that your advertisement \"%title\" is now actively being displayed on the %sitename website.\n\n  Your advertisement has been viewed %global_views times and clicked %global_clicks times since it was activated on %activated_large.\n\n  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"),
          );
        case '-active':
          return array(
            'subject' => t('[%sitename ad] activation notification'),
            'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification to inform you that your advertisement \"%title\" will be actively displayed on the %sitename website on %autoactivate_large.\n\n  You can view statistics about this advertisement or update this notification at the following url:\n    %url\n\nRegards,\n The %sitename Team\n\n-\n%siteurl"),
          );
        case 'click':
          return array(
            'subject' => t('[%sitename ad] %event notification'),
            'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification to inform you that your advertisement \"%title\" on the %sitename website has been clicked.\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"),
          );
        case 'approved':
          return array(
            'subject' => t('[%sitename ad] %event notification'),
            'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification to inform you that your advertisement \"%title\" on the %sitename website has been approved.\n\n  You can view statistics about this advertisement at the following url:\n    %url\n\nRegards,\n The %sitename Team\n\n-\n%siteurl"),
          );
        case 'denied':
          return array(
            'subject' => t('[%sitename ad] %event notification'),
            'body' => t("Hello %owner_name,\n\n  This is an automatically generated notification to inform you that your advertisement \"%title\" on the %sitename website has been denied and will not be displayed.\n\n  You can view statistics about this advertisement at the following url:\n    %url\n\nRegards,\n The %sitename Team\n\n-\n%siteurl"),
          );
      }
      break;
  }
}

/****/
function _ad_check_install() {

  // Verify serve.php exists and is readable.
  $adserve = variable_get('adserve', '');
  $adserveinc = variable_get('adserveinc', '');
  if (!file_exists($adserve)) {

    // The serve.php file should be in the same directory as the ad.module.
    $adserve = drupal_get_path('module', 'ad') . '/serve.php';
    variable_set('adserve', $adserve);
  }
  if (!is_readable($adserve)) {
    variable_set('adserve', '');
    drupal_set_message(t('Failed to read the required file %filename.  Please make the file readable by the webserver process.  No ads can be displayed until this problem is resolved.', array(
      '%filename' => $adserve,
    )), 'error');
  }
  if (!file_exists($adserveinc)) {

    // The adserve.inc file should be in the same directory as the ad.module.
    $adserveinc = drupal_get_path('module', 'ad') . '/adserve.inc';
    variable_set('adserveinc', $adserveinc);
  }
  if (!is_readable($adserveinc)) {
    variable_set('adserveinc', '');
    drupal_set_message(t('Failed to read the required file %filename.  Please make the file readable by the webserver process.  No ads can be displayed until this problem is resolved.', array(
      '%filename' => $adserveinc,
    )), 'error');
  }

  // Validate vid in vocabulary table.
  $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module = 'ad'"));
  if ($vid != variable_get('ad_group_vid', '')) {
    drupal_set_message(t('Invalid vocabulary defined for advertisements, attempting to auto-fix.'), 'error');
    if ($vid) {
      db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d OR type = 'ad'", variable_get('ad_group_vid', ''));
      variable_set('ad_group_vid_restore', variable_get('ad_group_vid', ''));
    }
    variable_del('ad_group_vid');
  }
  else {

    // Validate vid in vocabulary_node_types table.
    $result = db_query("SELECT vid FROM {vocabulary_node_types} WHERE type = 'ad'");
    $found = FALSE;
    while ($vocab = db_fetch_object($result)) {
      if ($vocab->vid == variable_get('ad_group_vid', '')) {
        $found = TRUE;
      }
    }
    if (!$found) {
      drupal_set_message(t('Missing vocabulary node type for advertisements, attempting to auto-fix.'), 'error');
      db_query("DELETE FROM {vocabulary_node_types} WHERE vid = %d OR type = 'ad'", variable_get('ad_group_vid', ''));
      db_query("DELETE FROM {vocabulary} WHERE vid = %d", variable_get('ad_group_vid', ''));
      variable_set('ad_group_vid_restore', variable_get('ad_group_vid', ''));
      variable_del('ad_group_vid');
    }
  }
  _ad_get_vid();

  // Preserve old ad groups, if any.
  if (($old = variable_get('ad_group_vid_restore', '')) && ($vid = variable_get('ad_group_vid', ''))) {
    drupal_set_message(t('Restoring orphaned ad group configuration.'));
    db_query('UPDATE {term_data} SET vid = %d WHERE vid = %d', $vid, $old);
    variable_set('ad_group_vid_restore', '');
  }
  $result = db_query("SELECT rid FROM {permission} WHERE perm LIKE '%%show advertisements%%'");
  if (!db_num_rows($result)) {
    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/access'),
    )));
  }

  // Allow modules to define an action to take each time an ad is served.
  // When modules define 'adserve_select' or 'adserve_filter', they must set
  // the 'function' and 'path' parameters.  The 'weight' parameter can
  // optionally be set.
  //  function: the function to call when serving an add
  //  path: the path to the include file where $function is defined
  // Modules can define actions that happen when advertisements are served.
  // Currently support actions are:
  //  - init_text   (display content before displaying ads) // TODO
  //  - select      (alter which ads are selected to be displayed) // TODO
  //  - filter      (filter selected ads before they are displayed) // TODO
  //  - exit_text   (display content after displaying ads) // TODO
  $hooks = array(
    'init_text',
    'select',
    'filter',
    'exit_text',
  );
  foreach ($hooks as $hook) {
    $adserve_actions = module_invoke_all('adapi', "adserve_{$hook}", array());
    $actions = array();
    foreach ($adserve_actions as $name => $action) {
      if (is_numeric($action['weight'])) {
        $weight = $action['weight'];
      }
      else {

        // weight is an optional, defaults to 0
        $weight = $action['weight'] = 0;
      }
      $actions["{$weight}.{$name}"] = $action;
      $actions["{$weight}.{$name}"]['name'] = $name;
    }

    // order actions by weight (multiple same-weight actions sorted by alpha)
    ksort($actions);
    variable_set("adserve_{$hook}", serialize($actions));
  }
  module_invoke_all('adapi', 'check_install', array());
}

/**
 * Creates a vocabulary for use by ad groups if not already created.
 */
function _ad_get_vid() {
  $vid = variable_get('ad_group_vid', '');
  if (empty($vid)) {

    // No vid stored in the variables table, check if one even exists.
    $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module = '%s'", 'ad'));
    if (!$vid) {

      // No vid, so we create one.
      $edit = array(
        'name' => 'Ad groups',
        'multiple' => 1,
        'required' => 0,
        'hierarchy' => 0,
        'relations' => 0,
        'module' => 'ad',
        'nodes' => array(
          'ad' => 1,
        ),
      );
      taxonomy_save_vocabulary($edit);
      $vid = $edit['vid'];
    }

    // Save the vid for next time.
    variable_set('ad_group_vid', $vid);
  }
  return $vid;
}

/**
 * Returns a form for adding an ad group.
 */
function ad_admin_group_form($group = array()) {
  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_id, $form_values) {
  $status = taxonomy_save_term($form_values);
  switch ($status) {
    case SAVED_NEW:
      $groups = variable_get('ad_groups', array());
      $groups[] = $form_values['tid'];
      variable_set('ad_groups', $groups);
      drupal_set_message(t('Created new ad group %term.', array(
        '%term' => $form_values['name'],
      )));
      break;
    case SAVED_UPDATED:
      drupal_set_message(t('The ad group %term has been updated.', array(
        '%term' => $form_values['name'],
      )));
  }
  return 'admin/content/ad/groups';
}

/**
 * Returns a confirmation page when deleting an ad group and all of its ads.
 */
function ad_confirm_group_delete($term) {
  $form['tid'] = array(
    '#type' => 'value',
    '#value' => $term['tid'],
  );
  $form['name'] = array(
    '#type' => 'value',
    '#value' => $term['name'],
  );
  return confirm_form($form, t('Are you sure you want to delete the ad group %name?', array(
    '%name' => $term['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_id, $form_values) {
  taxonomy_del_term($form_values['tid']);
  drupal_set_message(t('The ad group %term has been deleted.', array(
    '%term' => $form_values['name'],
  )));
  watchdog('ad', t('mailarchive: deleted %term ad group.', array(
    '%term' => $form_values['name'],
  )));
  return 'admin/content/ad/groups';
}

/**
 * Builds the necessary HTML to display an image-based view counter.
 */
function ad_display_image($ad, $css = TRUE) {
  global $base_url;
  $adserve = variable_get('adserve', '');
  $cache = variable_get('ad_cache', 'none');
  $variables = "?o=image";
  if (is_object($ad)) {
    $aid = $ad->aid;
  }
  else {

    /**
     * No ad is specified, so we're just tracking traffic.
     */
    $aid = 0;
  }
  $variables .= "&amp;a={$aid}";
  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" />';
  if ($css) {
    return '<div class="ad-image-counter">' . $output . '</div>';
  }
  else {
    return $output;
  }
}

/**
 * Retrieve the group name from the nid.
 */
function _ad_get_group($nid) {
  static $groups = array();
  if (!isset($groups[$nid])) {
    $result = db_query('SELECT d.name FROM {term_data} d LEFT JOIN {term_node} n ON d.tid = n.tid WHERE n.nid = %d AND d.vid = %d', $nid, _ad_get_vid());
    while ($term = db_fetch_object($result)) {
      $terms[] = $term->name;
    }
    if (!empty($terms)) {
      $groups[$nid] = implode(', ', $terms);
    }
    else {
      $groups[$nid] = t('default');
    }
  }
  return $groups[$nid];
}

Functions

Namesort descending Description
ad Use this function to display ads from a specified group.
ad_access
ad_adapi
ad_add Present a list of ad types to choose from.
ad_admin_ads Provide a filterable list of advertisements.
ad_admin_ads_submit Submit the ad administration update form.
ad_admin_ads_validate Must select an ad if performing an operation.
ad_admin_configure_settings Display a form for the ad module settings.
ad_admin_configure_settings_submit Save updated values from settings form.
ad_admin_configure_settings_validate Validate form settings, calling attention to any illogical configurations.
ad_admin_groups_list
ad_admin_group_form Returns a form for adding an ad group.
ad_admin_group_form_submit Save a newly created ad group.
ad_admin_list Build default ad administration page.
ad_adnotifyapi Implement ad notify api _hook.
ad_ad_operations Implementation of hook_ad_operations().
ad_block Drupal _block hook.
ad_build_filter_query Build query for ad administration filters based on session.
ad_click_details Display details about a specific click.
ad_click_history Display click history for advertisemet, if user has necessary permission.
ad_confirm_group_delete Returns a confirmation page when deleting an ad group and all of its ads.
ad_confirm_group_delete_submit Delete ad group.
ad_cron Drupal _cron hook.
ad_display_image Builds the necessary HTML to display an image-based view counter.
ad_filters List ad administration filters that can be applied.
ad_filter_form Return form for advertisement administration filters.
ad_filter_form_submit Process result from ad administration filter form.
ad_form Drupal _form hook.
ad_form_alter Drupal _form_alter() hook.
ad_groups_list Return an array of all groups, or a specific group.
ad_help Implementation of hook_help().
ad_link_attributes Ad API Helper Function: Append all necessary attributes to <a> tags.
ad_link_nofollow Ad API Helper Function: Append rel="nofollow" if globally enabled.
ad_link_target Ad API Helper Function: Provide XHTML-strict-compatible target window onclick-handlers based on global configuration.
ad_menu Implementation of hook_menu().
ad_multiple_delete_confirm Display a form to confirm whether to really delete the selected ads.
ad_multiple_delete_confirm_submit Perform the actual ad deletions.
ad_nodeapi Drupal _nodeapi hook.
ad_node_info
ad_no_global_settings 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.
ad_operations_callback Callback function for admin mass approving ads. TODO: Update activated and expired when appropriate. TODO: Publish/unpublish nodes when appropriate.
ad_perm Drupal _perm hook. Establishes permissions used by this module.
ad_permission Stub for ad_permission module, used to determine whether the user has a given privilege. If the ad_permission module is not enabled, all permissions are granted.
ad_rebuild_cache Force the cache to be flushed.
ad_redirect Update click counter then redirect host to ad's target URL.
ad_statistics Calculate statistics for the given advertisements. TODO: Introduce caching to make this more efficient.
ad_statistics_increment Increment action counter.
ad_status_array Return an array with all status values user has permission to set. A user with 'administer advertisements' permission can update any status.
theme_ad_admin_ads Theme ad administration overview.
theme_ad_display Function to display the actual advertisement to the screen. Wrap it in a theme function to make it possible to customize in your own theme.
theme_ad_filters Theme ad administration filter selector.
theme_ad_filter_form Theme ad administration filter form.
theme_ad_statistics_display
theme_ad_status_display Display the status of the currently viewed ad.
theme_node_ad
_ad_check_install
_ad_get_group Retrieve the group name from the nid.
_ad_get_vid Creates a vocabulary for use by ad groups if not already created.