classified.scheduled.inc in Classified Ads 7.3
Same filename and directory in other branches
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.incView 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 : 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 addTag('node_access'): this query can be run at any time by anyone.
*
* We SELECT first and UPDATE later in order to get a notification list.
*
* @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.
*
* @throws \Exception
*/
function _classified_scheduled_build_expire($time = NULL) {
// Obtain the list of ads to expire.
$expires = _classified_get_time($time);
$q = db_select('node', 'n')
->comment(__FUNCTION__);
$cn = $q
->innerJoin('classified_node', 'cn', 'n.nid = cn.nid');
$results = $q
->fields('n', array(
'nid',
'title',
'uid',
))
->condition('n.type', 'classified')
->condition('n.status', 1)
->condition("{$cn}.expires", $expires, '<')
->execute();
$expired = array();
$nids = array();
foreach ($results as $result) {
$expired[$result->uid][$result->nid] = $result->title;
$nids[] = $result->nid;
}
unset($results);
$count = count($nids);
if ($count) {
/*
* Now perform the expiration. SQL*99 does not include join in updates, so
* DBTNG does not have them either.
*
* http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15464
*/
$transaction = db_transaction();
try {
$q = db_update('node')
->comment(__FUNCTION__)
->fields(array(
'status' => 0,
))
->condition('nid', $nids, 'IN');
$touched = $q
->execute();
db_update('node_revision')
->comment(__FUNCTION__)
->fields(array(
'status' => 0,
))
->condition('nid', $nids, 'IN')
->execute();
db_update('classified_node')
->comment(__FUNCTION__)
->fields(array(
'notify' => 0,
))
->condition('nid', $nids, 'IN')
->execute();
} catch (Exception $e) {
$transaction
->rollback();
watchdog_exception('classified', $e);
throw $e;
}
if (function_exists('_trigger_node')) {
$nodes = node_load_multiple($nids);
foreach ($nodes as $node) {
_trigger_node($node, 'node_update');
}
}
// DBTNG controlled commit.
unset($transaction);
watchdog('classified', 'Expiration unpublished @count ads: @expired', array(
'@count' => $touched,
'@expired' => var_export($expired, TRUE),
), WATCHDOG_INFO);
}
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.
*
* No addTag('node_access'): this is an administrative function, that needs full
* access.
*
* @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);
/** @var \SelectQueryInterface $q */
$q = db_select('node', 'n')
->comment(__FUNCTION__);
$cn = $q
->innerJoin('classified_node', 'cn', 'n.nid = cn.nid');
$q
->fields('n', array(
'nid',
'uid',
'title',
));
switch ($kind) {
case 'half-life':
// cn.notify < half-life, now > half-life.
$q
->condition('n.type', 'classified')
->condition('n.status', 0, '!=')
->where("{$cn}.notify < (n.changed + {$cn}.expires) / 2")
->where("(n.changed + {$cn}.expires) / 2 < {$now}");
break;
case 'pre-expire':
// cn.notify < expiration - 1 day, now > expiration - 1 day.
$q
->condition('n.type', 'classified')
->condition('n.status', 0, '!=')
->where("{$cn}.notify < {$cn}.expires - 86400")
->condition("{$cn}.expires", $now + 86400, '<');
break;
case 'pre-purge':
// cn.notify < purge - 1 day, now > purge - 1 day
// 'grace' is in days.
$grace = (_classified_get('grace') - 1) * 24 * 60 * 60;
$q
->condition('n.type', 'classified')
->condition('n.status', 0)
->where("{$cn}.notify < {$cn}.expires + {$grace} - 86400")
->condition("{$cn}.expires", $now - $grace, '<');
break;
default:
watchdog('classified', 'Invalid notify type requested: @kind', array(
'@kind' => $kind,
), WATCHDOG_WARNING);
break;
}
$results = $q
->execute();
$notified = array();
foreach ($results as $result) {
$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 $user_notified) {
foreach (array_keys($user_notified) as $nid) {
$updated[] = $nid;
}
}
$q = db_update('classified_node')
->comment(__FUNCTION__)
->fields(array(
'notify' => $now,
))
->condition('nid', $updated, 'IN')
->execute();
watchdog('classified', 'Updated notification timestamp on @count ads.', array(
'@count' => count($updated),
), 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.
*
* There is no addTag('node_access') because this is an administrative
* operation, that can be triggered by any user and needs to access all matching
* nodes, not only those for which access is granted.
*
* @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.
*
* @throws \Exception
*/
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;
$q = db_select('node', 'n')
->comment(__FUNCTION__);
$cn = $q
->innerJoin('classified_node', 'cn', 'n.nid = cn.nid');
$results = $q
->fields('n', array(
'nid',
'title',
'uid',
))
->condition('n.type', 'classified')
->condition("{$cn}.expires", $limit, '<')
->execute()
->fetchAll();
$count = count($results);
if ($count) {
$ads = array();
foreach ($results as $result) {
$ads[$result->uid][$result->nid] = $result->title;
$deleted[] = $result->nid;
}
// 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.
//
// About coder false positive:
// http://drupal.org/node/224333#drupal_set_session
$messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : array();
global $user;
$saved_account = $user;
drupal_save_session(FALSE);
$user = user_load(1);
// May invoke drupal_set_message(), hence the hiding code.
node_delete_multiple($deleted);
$user = $saved_account;
drupal_save_session(TRUE);
// Coder: false positive.
$_SESSION['messages'] = $messages;
watchdog('classified', "Deleted @count nodes: @deleted", array(
'@count' => $count,
'@deleted' => var_export($deleted, 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
* A string instead of a render array, because there is not much to say.
*
* @throws \Exception
*/
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
* A string instead of a render array, because there is 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
* A string instead of a render array, because there is not much to say.
*
* @throws \Exception
*/
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
Name | 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. |