You are here

classified.scheduled.inc in Classified Ads 6.3

Same filename and directory in other branches
  1. 7.3 classified.scheduled.inc

Scheduled operations for classified.module

Can be invoked from the web UI or from Drush:

  • purge
  • expiration
  • notifications

@copyright (c) 2010-2011 Ouest Systemes Informatiques (OSInet)

@license General Public License version 2 or later

Original code, not derived from the ed_classified module.

File

classified.scheduled.inc
View source
<?php

/**
 * @file
 * Scheduled operations for classified.module
 *
 * Can be invoked from the web UI or from Drush:
 * - purge
 * - expiration
 * - notifications
 *
 * @copyright (c) 2010-2011 Ouest Systemes Informatiques (OSInet)
 *
 * @license General Public License version 2 or later
 *
 * Original code, not derived from the ed_classified module.
 */

/**
 * Helper to return current time or a chosen time.
 *
 * @param int $time
 *   A UNIX timestamp.
 *
 * @return int
 *   A UNIX timestamp.
 */
function _classified_get_time($time = NULL) {
  return isset($time) ? $time : $_SERVER['REQUEST_TIME'];
}

/**
 * Unpublish nodes past their expiration date.
 *
 * The test on n.type is not required, since the inner join will only return
 * such nodes anyway, but allows the query to take advantage of the core index
 * on node.type
 *
 * Reset notify time to 0 because this field is only used for interim
 * notifications, not expire/purge.
 *
 * No db_rewrite_sql: this query can be run at any time by anyone.
 *
 * @TODO check whether the update/join syntax works on non-MySQL DB engines
 *
 * @param int $time
 *   A UNIX timestamp. Normally not set: this was added for testing purposes.
 *
 * @return array
 *   A per-user array of per-nid expired nodes titles.
 */
function _classified_scheduled_build_expire($time = NULL) {

  // Obtain the list of ads to expire.
  $expires = _classified_get_time($time);
  $sq = <<<EOT
    SELECT n.nid, n.title, n.uid
    FROM {node} n
      INNER JOIN {classified_node} cn ON n.nid = cn.nid
    WHERE
      n.type = 'classified' AND n.status = 1
      AND cn.expires < %d
EOT;
  $q = db_query($sq, $expires);
  $count1 = 0;
  $expired = array();
  while ($o = db_fetch_object($q)) {
    $expired[$o->uid][$o->nid] = $o->title;
    $count1++;
  }

  // Now perform the expiration.
  $sq = <<<EOT
    UPDATE {node} n
      INNER JOIN {classified_node} cn ON n.nid = cn.nid
    SET n.status = 0, cn.notify = 0
    WHERE
      n.type = 'classified' AND n.status = 1
      AND cn.expires < %d
EOT;
  $sts = db_query($sq, $expires);
  $count2 = db_affected_rows();

  // should match $count1
  if ($count1 || $count2) {
    watchdog('classified', 'Expiration unpublished @count2 ads (@count1 scheduled): @expired', array(
      '@count2' => $count2,
      '@count1' => $count1,
      '@expired' => var_export($expired, TRUE),
    ), $count1 == $count2 ? WATCHDOG_INFO : WATCHDOG_WARNING);
  }
  else {
    watchdog('classified', 'Expiration check did not find any ad to expire.', NULL, WATCHDOG_INFO);
  }
  drupal_alter('classified_expire', $expired);
  return $expired;
}

/**
 * Build one of the various notification lists.
 *
 * All this work can be skipped if no module implements
 * hook_classified_notify(): in such a case, notifications do not happen, and
 * there is no reason to update the notify date since notifications are not
 * being sent.
 *
 * @param string $kind
 *   The kind of notification to send, from the module-defined list of kinds.
 * @param int $time
 *   A UNIX timestamp. Normally not set: this was added for testing purposes.
 *
 * @return array
 *   A per-user array of per-nid node titles to be notified.
 */
function _classified_scheduled_build_notify($kind, $time = NULL) {
  $notified = array();
  $modules = module_implements('classified_notify_alter');
  if (empty($modules)) {
    return $notified;
  }
  $now = _classified_get_time($time);
  switch ($kind) {
    case 'half-life':

      // cn.notify < half-life, now > half-life.
      $sq = <<<EOT
        SELECT n.nid, n.uid, n.title, cn.notify
        FROM {node} n
          INNER JOIN {classified_node} cn ON n.nid = cn.nid
        WHERE
          n.type = 'classified' AND n.status != 0
          AND cn.notify < (n.changed + cn.expires) / 2
          AND (n.changed + cn.expires) / 2 < %d
EOT;

      // Clean up whitespace for regexps.
      $sq = trim(str_replace("\n", ' ', $sq));
      $sq = db_rewrite_sql($sq);
      $q = db_query($sq, $now);
      break;
    case 'pre-expire':

      // cn.notify < expiration - 1 day, now > expiration - 1 day.
      $sq = <<<EOT
        SELECT n.nid, n.uid, n.title, cn.notify
        FROM {node} n
          INNER JOIN {classified_node} cn ON n.nid = cn.nid
        WHERE
          n.type = 'classified' AND n.status != 0
          AND cn.notify < cn.expires - 86400
          AND cn.expires < %d
EOT;

      // Clean up whitespace for regexps.
      $sq = trim(str_replace("\n", ' ', $sq));
      $sq = db_rewrite_sql($sq);
      $q = db_query($sq, $now + 86400);
      break;
    case 'pre-purge':

      // cn.notify < purge - 1 day, now > purge - 1 day
      $sq = <<<EOT
      SELECT n.nid, n.uid, n.title, cn.notify
        FROM {node} n
          INNER JOIN {classified_node} cn ON n.nid = cn.nid
        WHERE
          n.type = 'classified' AND n.status = 0
          AND cn.notify < cn.expires + %d - 86400
          AND cn.expires + %d < %d
EOT;

      // Clean up whitespace for regexps.
      $sq = trim(str_replace("\n", ' ', $sq));
      $sq = db_rewrite_sql($sq);

      // 'grace' is in days.
      $grace = (_classified_get('grace') - 1) * 24 * 60 * 60;
      $q = db_query($sq, $grace, $grace, $now);
      break;
    default:
      watchdog('classified', 'Invalid notify type requested: @kind', array(
        '@kind' => $kind,
      ), WATCHDOG_WARNING);
      break;
  }
  $notified = array();
  while ($result = db_fetch_object($q)) {
    $notified[$result->uid][$result->nid] = $result->title;
  }

  // Alter before updating: allow modules to modify the notification list.
  drupal_alter('classified_notify', $notified, $kind);

  // Avoid building an empty update query.
  if (empty($notified)) {
    return $notified;
  }
  $updated = array();
  foreach ($notified as $uid => $user_notified) {
    foreach (array_keys($user_notified) as $nid) {
      $updated[] = $nid;
    }
  }
  $placeholders = db_placeholders($updated);
  $sq = <<<EOT
    UPDATE {classified_node}
    SET notify = %d
    WHERE nid IN ({<span class="php-variable">$placeholders</span>})
EOT;

  // Single array format query parameter needed with a placeholders array.
  array_unshift($updated, $now);
  $q = db_query($sq, $updated);
  $touched = db_affected_rows();
  watchdog('classified', 'Updated notification timestamp on @count ads.', array(
    '@count' => $touched,
    WATCHDOG_INFO,
  ));
  return $notified;
}

/**
 * Purge nodes past their expiration date + grace period
 *
 * Selected nodes: expires + grace < now => expires < now - grace
 *
 * The test on n.type is not required, since the inner join will only return
 * such nodes anyway, but allows the query to take advantage of the core index
 * on node.type
 *
 * No db_rewrite_sql: this query can be run at any time by anyone.
 *
 * @param int $time
 *   A UNIX timestamp. Normally not set: this was added for testing purposes.
 *
 * @return array
 *   A per-user array of per-nid node titles to be deleted.
 */
function _classified_scheduled_build_purge($time = NULL) {
  $grace = _classified_get('grace');
  if ($grace == -1) {
    return array();
  }
  $limit = _classified_get_time($time) - $grace * 24 * 60 * 60;
  $sq = <<<EOT
    SELECT n.nid, n.title, n.uid
    FROM {node} n
      INNER JOIN {classified_node} cn ON n.nid = cn.nid
    WHERE
      n.type = 'classified' AND cn.expires < %d
EOT;
  $q = db_query($sq, $limit);
  $ads = array();
  $count = 0;

  // Hide message information, since the page can be triggered by any user,
  // but needs to run as admin, and protect misc session content as well.
  $messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : array();
  global $user;
  $saved_account = $user;
  session_save_session(FALSE);
  $user = user_load(1);
  while ($o = db_fetch_object($q)) {
    $ads[$o->uid][$o->nid] = $o->title;
    node_delete($o->nid);

    // invokes drupal_set_message(), hence the hiding code
    $count++;
  }
  $user = $saved_account;
  session_save_session(TRUE);
  $_SESSION['messages'] = $messages;
  if ($count) {
    watchdog('classified', "Deleted @count nodes: @deleted", array(
      '@count' => $count,
      '@deleted' => var_export($ads, TRUE),
    ), WATCHDOG_INFO);
  }
  else {
    watchdog('classified', 'Purge did not find any ad to delete.', NULL, WATCHDOG_INFO);
    $ads = array();
  }
  drupal_alter('classified_purge', $ads);
  return $ads;
}

/**
 * Page callback for expirations
 *
 * @return string
 *   Not much to say.
 */
function _classified_scheduled_page_expire() {

  // Do not display results.
  _classified_scheduled_build_expire();
  drupal_set_breadcrumb(_classified_get_breadcrumb_by_term(NULL));
  return t('<p>Thanks for triggering our expiration process.</p>');
}

/**
 * Page callback for notifications
 *
 * @param string $kind
 *   A notification kind from _classified_get_notify_kinds().
 *
 * @return string
 *   Not much to say.
 */
function _classified_scheduled_page_notify($kind) {
  _classified_scheduled_build_notify($kind);
  drupal_set_breadcrumb(_classified_get_breadcrumb_by_term(NULL));
  return t('<p>Thanks for triggering our @kind notification process.</p>', array(
    '@kind' => $kind,
  ));
}

/**
 * Page callback for purges.
 *
 * @return string
 *   Not much to say.
 */
function _classified_scheduled_page_purge() {

  // Do not display results.
  _classified_scheduled_build_purge();
  drupal_set_breadcrumb(_classified_get_breadcrumb_by_term(NULL));
  return t('<p>Thanks for triggering our purge process.</p>');
}

Functions

Namesort descending Description
_classified_get_time Helper to return current time or a chosen time.
_classified_scheduled_build_expire Unpublish nodes past their expiration date.
_classified_scheduled_build_notify Build one of the various notification lists.
_classified_scheduled_build_purge Purge nodes past their expiration date + grace period
_classified_scheduled_page_expire Page callback for expirations
_classified_scheduled_page_notify Page callback for notifications
_classified_scheduled_page_purge Page callback for purges.