| 1 |
<?php
|
| 2 |
// $Id: ad_memcache.module,v 1.1.2.9.2.6 2009/02/16 23:12:28 jeremy Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* A plug in for the ad.module, integrating the ad module with memcache.
|
| 7 |
*
|
| 8 |
* Copyright (c) 2005-2009.
|
| 9 |
* Jeremy Andrews <jeremy@tag1consulting.com>.
|
| 10 |
*/
|
| 11 |
|
| 12 |
/**
|
| 13 |
* Implementation of hook_help().
|
| 14 |
*/
|
| 15 |
function ad_memcache_help($path, $arg) {
|
| 16 |
$output = '';
|
| 17 |
switch ($path) {
|
| 18 |
case 'admin/modules#description':
|
| 19 |
$output = t('Utilize memcached to improve the performance of the ad module.');
|
| 20 |
break;
|
| 21 |
|
| 22 |
}
|
| 23 |
return $output;
|
| 24 |
}
|
| 25 |
|
| 26 |
/**
|
| 27 |
* Implementation of hook_adcacheapi().
|
| 28 |
*/
|
| 29 |
function ad_memcache_adcacheapi($op, &$node = array()) {
|
| 30 |
switch ($op) {
|
| 31 |
case 'method':
|
| 32 |
ad_memcache_sync();
|
| 33 |
ad_memcache_build();
|
| 34 |
return array('memcache' => t('Memcache'));
|
| 35 |
case 'description':
|
| 36 |
return t('Memcache allows improved performance by caching data directly in RAM.');
|
| 37 |
case 'settings':
|
| 38 |
$form['memcache'] = array(
|
| 39 |
'#type' => 'fieldset',
|
| 40 |
'#title' => t('Memcache settings'),
|
| 41 |
'#collapsible' => TRUE,
|
| 42 |
'#collapsed' => (variable_get('ad_cache', 'none') == 'memcache') ? FALSE : TRUE,
|
| 43 |
);
|
| 44 |
$period = drupal_map_assoc(array(60,120,180,240,300,600,900,1800,2700,3600,10800,21600,43200,86400), 'format_interval');
|
| 45 |
$form['memcache']['ad_memcache_sync'] = array(
|
| 46 |
'#type' => 'select',
|
| 47 |
'#title' => t('Sync frequency'),
|
| 48 |
'#default_value' => variable_get('ad_memcache_sync', 600),
|
| 49 |
'#options' => $period,
|
| 50 |
'#description' => t('Specify how often statistics stored in RAM should be synced to the database (requires cron runs with the same or greater frequency). The longer you store data in memcache, the more data you risk loosing in the event of a system failure. This configuration option is only relevant if memcache is enabled.'),
|
| 51 |
);
|
| 52 |
// Sanity tests...
|
| 53 |
if (variable_get('ad_cache', 'none') == 'memcache') {
|
| 54 |
$sync = variable_get('ad_memcache_sync', 600);
|
| 55 |
$cron_last = variable_get('cron_last', NULL);
|
| 56 |
if (is_numeric($cron_last)) {
|
| 57 |
if (time() - $cron_last > $sync) {
|
| 58 |
drupal_set_message(t('Memcache warning: your last cron run was !time ago. Advertisement impression data is only synchronized into the database when cron runs. You are risking data loss. To learn more about how Drupal cron works, please check the online help pages for <a href="@url">configuring cron jobs</a>. You can also !manually.', array('@url' => 'http://drupal.org/cron', '!sync' => format_interval($sync), '!time' => format_interval(time() - $cron_last), '!manually' => l(t('run cron manually'), 'admin/reports/status/run-cron', array('query' => drupal_get_destination())))), 'error');
|
| 59 |
}
|
| 60 |
}
|
| 61 |
else {
|
| 62 |
drupal_set_message(t('Memcache warning: Cron has not run. Advertisement impression data is only synchronized into the database when cron runs. You are risking data loss. It appears cron jobs have not been setup on your system. Please check the help pages for <a href="@url">configuring cron jobs</a>. You can also !manually.', array('@url' => 'http://drupal.org/cron', '!manually' => l(t('run cron manually'), 'admin/reports/status/run-cron', array('query' => drupal_get_destination())))), 'error');
|
| 63 |
}
|
| 64 |
}
|
| 65 |
return $form;
|
| 66 |
case 'settings_submit':
|
| 67 |
variable_set('ad_memcache_sync', $node['ad_memcache_sync']);
|
| 68 |
break;
|
| 69 |
|
| 70 |
case 'insert':
|
| 71 |
case 'update':
|
| 72 |
case 'delete':
|
| 73 |
if (variable_get('ad_cache', 'none') == 'memcache') {
|
| 74 |
ad_memcache_sync_ad($node->nid);
|
| 75 |
ad_memcache_build($node);
|
| 76 |
}
|
| 77 |
break;
|
| 78 |
}
|
| 79 |
}
|
| 80 |
|
| 81 |
/**
|
| 82 |
* Regularily syncronize counters into RAM.
|
| 83 |
*/
|
| 84 |
function ad_memcache_cron() {
|
| 85 |
$ad_memcache_timestamp = variable_get('ad_memcache_timestamp', '');
|
| 86 |
if ((time() - $ad_memcache_timestamp) >= variable_get('ad_memcache_sync', 000)) {
|
| 87 |
require_once(drupal_get_path('module', 'ad') .'/adserve.inc');
|
| 88 |
ad_memcache_sync();
|
| 89 |
}
|
| 90 |
|
| 91 |
$ad_memcache_build = variable_get('ad_memcache_build', '');
|
| 92 |
// rebuild cache every 12 hours
|
| 93 |
// TODO: Make configurable
|
| 94 |
if ((time() - $ad_memcache_build) >= 43200 || TRUE) {
|
| 95 |
ad_memcache_build();
|
| 96 |
}
|
| 97 |
}
|
| 98 |
|
| 99 |
/**
|
| 100 |
* Load advertisements into memory.
|
| 101 |
*/
|
| 102 |
function ad_memcache_sync() {
|
| 103 |
variable_set('ad_memcache_timestamp', time());
|
| 104 |
if (_ad_memcache_invoke('validate')) {
|
| 105 |
$result = db_query("SELECT aid, adtype FROM {ads} WHERE adstatus = 'active'");
|
| 106 |
while ($ad = db_fetch_object($result)) {
|
| 107 |
ad_memcache_sync_ad($ad->aid);
|
| 108 |
}
|
| 109 |
// Sync counters.
|
| 110 |
ad_memcache_sync_ad(0);
|
| 111 |
}
|
| 112 |
else {
|
| 113 |
drupal_set_message(t('!module: Unable to syncronize cache.', array('!module' => 'ad_memcache.module')), 'error');
|
| 114 |
}
|
| 115 |
}
|
| 116 |
|
| 117 |
/**
|
| 118 |
* Syncronize counts for given advertisement with database.
|
| 119 |
*/
|
| 120 |
function ad_memcache_sync_ad($aid) {
|
| 121 |
if (!_ad_memcache_invoke('validate')) {
|
| 122 |
drupal_set_message(t('!module: Unable to syncronize cache.', array('!module' => 'ad_memcache.module')), 'error');
|
| 123 |
return;
|
| 124 |
}
|
| 125 |
|
| 126 |
if (!ad_memcache_lock("ad-counters-$aid")) {
|
| 127 |
// Another process is already updating these values.
|
| 128 |
return;
|
| 129 |
}
|
| 130 |
$counters = ad_memcache_get("ad-counters-$aid");
|
| 131 |
if (!is_array($counters)) {
|
| 132 |
ad_memcache_unlock("ad-counters-$aid");
|
| 133 |
// There's nothing currently in memory for this ad.
|
| 134 |
return;
|
| 135 |
}
|
| 136 |
ad_memcache_delete("ad-counters-$aid");
|
| 137 |
ad_memcache_unlock("ad-counters-$aid");
|
| 138 |
foreach ($counters as $map) {
|
| 139 |
list($action, $group, $hostid, $extra, $timestamp) = explode(':', $map);
|
| 140 |
if ($action && isset($group) && $hostid && $timestamp) {
|
| 141 |
$count = ad_memcache_get("ad-$action-$aid-$group-$hostid-$extra-$timestamp");
|
| 142 |
if ($count) {
|
| 143 |
ad_memcache_decrement("ad-$action-$aid-$group-$hostid-$extra-$timestamp", $count);
|
| 144 |
db_query("UPDATE {ad_statistics} SET count = count + %d WHERE aid = %d AND action = '%s' AND date = %d AND adgroup = '%s' AND hostid = '%s' AND extra = '%s'", $count, $aid, $action, $timestamp, $group, $hostid, $extra);
|
| 145 |
// If column doesn't already exist, we need to add it.
|
| 146 |
if (!db_affected_rows()) {
|
| 147 |
db_query("INSERT INTO {ad_statistics} (aid, date, action, adgroup, hostid, count, extra) VALUES(%d, %d, '%s', '%s', '%s', '%s', %d)", $aid, $timestamp, $action, $group, $hostid, $extra, $count);
|
| 148 |
// If another process already added this row our INSERT will fail, if
|
| 149 |
// so we still need to increment it so we don't loose a count.
|
| 150 |
if (!db_affected_rows()) {
|
| 151 |
db_query("UPDATE {ad_statistics} SET count = count + %d WHERE aid = %d AND action = '%s' AND date = %d AND adgroup = '%s' AND hostid = '%s' AND extra = '%s'", $count, $aid, $action, $timestamp, $group, $hostid, $extra);
|
| 152 |
}
|
| 153 |
}
|
| 154 |
}
|
| 155 |
// If counting ad impressions, see if we've hit a limit
|
| 156 |
if ($action = 'view') {
|
| 157 |
$limits = db_fetch_object(db_query('SELECT activated, maxviews, maxclicks, adstatus FROM {ads} WHERE aid = %d', $aid));
|
| 158 |
if ($limits->adstatus == 'active') {
|
| 159 |
if ($limits->maxviews) {
|
| 160 |
$views = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d", $aid, date('YmdH', $limits->activated)));
|
| 161 |
if ($views >= $limits->maxviews) {
|
| 162 |
db_query("UPDATE {ads} SET adstatus = 'expired', autoexpire = 0, autoexpired = %d, expired = %d WHERE aid = %d", time(), time(), $aid);
|
| 163 |
ad_statistics_increment($aid, 'autoexpired');
|
| 164 |
ad_statistics_increment($aid, 'expired');
|
| 165 |
}
|
| 166 |
}
|
| 167 |
if ($limits->maxclicks) {
|
| 168 |
$clicks = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d", $aid, date('YmdH', $limits->activated)));
|
| 169 |
if ($clicks >= $limits->maxclicks) {
|
| 170 |
db_query("UPDATE {ads} SET adstatus = 'expired', autoexpire = 0, autoexpired = %d, expired = %d WHERE aid = %d", time(), time(), $aid);
|
| 171 |
ad_statistics_increment($aid, 'autoexpired');
|
| 172 |
ad_statistics_increment($aid, 'expired');
|
| 173 |
}
|
| 174 |
}
|
| 175 |
}
|
| 176 |
}
|
| 177 |
}
|
| 178 |
}
|
| 179 |
}
|
| 180 |
|
| 181 |
/**
|
| 182 |
* Caches ad information into memory.
|
| 183 |
*/
|
| 184 |
function ad_memcache_build($changed = NULL) {
|
| 185 |
variable_set('ad_memcache_build', time());
|
| 186 |
if (!_ad_memcache_invoke('validate')) {
|
| 187 |
drupal_set_message(t('!module: Unable to build cache.', array('!module' => 'ad_memcache.module')), 'error');
|
| 188 |
return;
|
| 189 |
}
|
| 190 |
|
| 191 |
if (is_object($changed) && isset($changed->aid)) {
|
| 192 |
// An advertisement has changed, rebuild cache on next cron run.
|
| 193 |
variable_set('ad_memcache_build', '');
|
| 194 |
}
|
| 195 |
else {
|
| 196 |
// Rebuilding entire cache.
|
| 197 |
$result = db_query("SELECT aid, adtype, redirect FROM {ads} WHERE adstatus = 'active' OR adstatus = 'approved' OR adstatus = 'offline'");
|
| 198 |
while ($ad = db_fetch_object($result)) {
|
| 199 |
$node = node_load($ad->aid);
|
| 200 |
$ad->display = module_invoke("ad_$ad->adtype", 'display_ad', $node);
|
| 201 |
ad_memcache_set("ad-aid-$ad->aid", $ad);
|
| 202 |
$ads[$ad->aid] = $ad->aid;
|
| 203 |
|
| 204 |
// Owner indexes.
|
| 205 |
$ad_owners = db_query('SELECT o.uid, h.hostid FROM {ad_owners} o LEFT JOIN {ad_hosts} h ON o.uid = h.uid WHERE aid = %d', $ad->aid);
|
| 206 |
$counter = 0;
|
| 207 |
while ($owner = db_fetch_object($ad_owners)) {
|
| 208 |
$owners[$owner->uid][$ad->aid] = $ad->aid;
|
| 209 |
ad_memcache_set("ad-$ad->aid-uid", $owner->uid);
|
| 210 |
}
|
| 211 |
|
| 212 |
$match = FALSE;
|
| 213 |
// Taxonomy index.
|
| 214 |
$terms = db_query('SELECT tid FROM {term_node} WHERE nid = %d', $ad->aid);
|
| 215 |
while ($term = db_fetch_object($terms)) {
|
| 216 |
$taxonomy[$term->tid][$ad->aid] = $ad->aid;
|
| 217 |
$match = TRUE;
|
| 218 |
}
|
| 219 |
if (!$match) {
|
| 220 |
$taxonomy[0][] = $ad->aid;
|
| 221 |
}
|
| 222 |
}
|
| 223 |
ad_memcache_set("ad-ads", $ads);
|
| 224 |
ad_memcache_set("ad-owners", $owners);
|
| 225 |
ad_memcache_set("ad-taxonomy", $taxonomy);
|
| 226 |
|
| 227 |
// HostID index
|
| 228 |
$owners = db_query('SELECT uid, hostid FROM {ad_hosts}');
|
| 229 |
while ($owner = db_fetch_object($owners)) {
|
| 230 |
ad_memcache_set("ad-hosts-$owner->uid", $owner->hostid);
|
| 231 |
if (($user = user_load(array('uid' => $owner->uid))) &&
|
| 232 |
(user_access('host remote advertisements', $user))) {
|
| 233 |
ad_memcache_set("ad-hostid-$owner->hostid", TRUE);
|
| 234 |
}
|
| 235 |
}
|
| 236 |
|
| 237 |
_debug_echo('Ad memcache: invoking external ad_build_cache hooks.');
|
| 238 |
$cache = array();
|
| 239 |
$external = module_invoke_all('ad_build_cache');
|
| 240 |
// TODO: Move helper function adserve_cache_build_hooks from adcache.inc to
|
| 241 |
// ad.module to share.
|
| 242 |
foreach ($external as $module => $return) {
|
| 243 |
// supported cache hooks
|
| 244 |
foreach (array('hook_init', 'hook_filter', 'hook_weight', 'hook_select',
|
| 245 |
'hook_init_text', 'hook_exit_text',
|
| 246 |
'hook_increment_extra') as $hook) {
|
| 247 |
if (isset($return[$hook]) && is_array($return[$hook])) {
|
| 248 |
$weight = isset($return[$hook]['weight']) ? (int)$return[$hook]['weight'] : 0;
|
| 249 |
$cache[$hook]['file'][$weight][] = $return[$hook]['file'];
|
| 250 |
$cache[$hook]['function'][$weight][] = $return[$hook]['function'];
|
| 251 |
unset($external[$module][$hook]);
|
| 252 |
}
|
| 253 |
}
|
| 254 |
}
|
| 255 |
$cache['external-hooks'] = $external;
|
| 256 |
// Always invoke hooks, they can decide to queue or act immediately.
|
| 257 |
ad_memcache_set('ad-cache-hooks', $cache);
|
| 258 |
|
| 259 |
}
|
| 260 |
}
|
| 261 |
|
| 262 |
function _ad_memcache_invoke($function = 'validate') {
|
| 263 |
if ($function == 'validate') {
|
| 264 |
if (!function_exists('memcache_add_server')) {
|
| 265 |
drupal_set_message(t('Memcache is not !installed. Ad cache memcache can not be used.', array('!installed' => l(t('properly installed'), 'admin/reports/status')), 'error'));
|
| 266 |
return FALSE;
|
| 267 |
}
|
| 268 |
}
|
| 269 |
require_once(drupal_get_path('module', 'ad_memcache') .'/ad_cache_memcache.inc');
|
| 270 |
return TRUE;
|
| 271 |
}
|
| 272 |
|