View source
<?php
DEFINE('RADIOACTIVITY_PERM_ADMIN', 'administer radioactivity');
require_once 'radioactivity.inc';
function radioactivity_perm() {
return array(
RADIOACTIVITY_PERM_ADMIN,
);
}
function radioactivity_help($section = '') {
$output = '';
switch ($section) {
case "admin/help#radioactivity":
$output = '<p>' . t("This module makes nodes radioactive! User activity increases radioactivity of a node " . "while time decays the radioactivity. The radioactivity is halved after a half-life period. The radioactivity is suitable for " . "better node activity metrics. Views support is built-in.") . '</p>';
break;
}
return $output;
}
function radioactivity_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
'path' => 'admin/settings/radioactivity',
'title' => t('Radioactivity'),
'description' => t('Configure settings for radioactivity.'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'radioactivity_admin_general_form',
),
'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
'type' => MENU_NORMAL_ITEM,
);
$items[] = array(
'path' => 'admin/settings/radioactivity/general',
'title' => t('General'),
'description' => t('Configure settings for radioactivity.'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'radioactivity_admin_general_form',
),
'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
'weight' => 0,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items[] = array(
'path' => 'admin/settings/radioactivity/list_profiles',
'title' => t('Decay profiles'),
'description' => t('List of decay profiles.'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'radioactivity_admin_profile_list',
),
'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
'weight' => 1,
'type' => MENU_LOCAL_TASK,
);
$items[] = array(
'path' => 'admin/settings/radioactivity/profile_new',
'title' => t('New profile'),
'description' => t('Add new decay profile.'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'radioactivity_admin_profile_form',
0,
),
'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
'weight' => 2,
'type' => MENU_LOCAL_TASK,
);
}
else {
if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'radioactivity' && substr(arg(3), 0, 8) == 'profile_' && is_numeric(substr(arg(3), 8))) {
$decay_profile_id = substr(arg(3), 8);
$decay_profiles = _radioactivity_get_decay_profiles();
$decay_profile = $decay_profiles[$decay_profile_id];
$items[] = array(
'path' => 'admin/settings/radioactivity/profile_' . $decay_profile_id,
'title' => t('Edit decay profile @label', array(
'@label' => $decay_profile["label"],
)),
'description' => t('Configure settings for decay profile @label.', array(
'@label' => $decay_profile["label"],
)),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'radioactivity_admin_profile_form',
$decay_profile_id,
),
'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
'weight' => 3,
'type' => MENU_LOCAL_TASK,
);
$items[] = array(
'path' => 'admin/settings/radioactivity/profile_' . $decay_profile_id . '/delete',
'title' => t('Delete profile'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'radioactivity_admin_delete_profile_form',
$decay_profile_id,
),
'access' => user_access(RADIOACTIVITY_PERM_ADMIN),
'type' => MENU_CALLBACK,
);
}
}
return $items;
}
function _radioactivity_set_decay_profiles($decay_profiles) {
if (function_exists('views_invalidate_cache')) {
views_invalidate_cache();
}
return variable_set("radioactivity_profiles", $decay_profiles);
}
function _radioactivity_get_decay_granularity() {
$granularity = (int) variable_get('radioactivity_decay_granularity', 0);
if ($granularity <= 0) {
$granularity = 600;
}
return $granularity;
}
function radioactivity_admin_general_form() {
$form = array();
$form['radioactivity_decay_granularity'] = array(
'#type' => 'textfield',
'#title' => t('Decay granularity (in seconds)'),
'#description' => t('This setting determines how often at most the radioactivity is decreased by the decay formula. ' . 'The shorter the time, the more accurate the modeling will be, but the more database ' . 'activity is required. The default (10 minutes) should be good starting point.'),
'#size' => 10,
'#required' => TRUE,
'#default_value' => _radioactivity_get_decay_granularity(),
);
$form['memcached'] = array(
'#type' => 'fieldset',
'#tree' => FALSE,
'#title' => t('Memcached acceleration'),
);
$memcached_ok = FALSE;
switch (radioactivity_determine_memcached_availability()) {
case RADIOACTIVITY_MEMCACHE_OK:
$memcached_availability_text = t('Ok');
$memcached_ok = TRUE;
break;
case RADIOACTIVITY_MEMCACHE_NO_BIN:
$memcached_availability_text = t('Cannot obtain memcache bin %bin', array(
'%bin' => 'radioactivity',
));
break;
case RADIOACTIVITY_MEMCACHE_NO_MODULE:
$memcached_availability_text = t('Memcache not enabled');
break;
}
$form['memcached']['availability'] = array(
'#type' => 'item',
'#title' => t('Memcached configuration status'),
'#value' => $memcached_availability_text,
);
$form['memcached']['radioactivity_memcached_enable'] = array(
'#type' => 'checkbox',
'#title' => t('Memcached acceleration for node views'),
'#description' => t('If this option is enabled, node views do not update radioactivity energies directly. Instead, ' . 'entry with minimal information is written to memcached. These entries are processed during cron runs.'),
'#default_value' => radioactivity_get_memcached_enable(),
'#disabled' => !$memcached_ok,
);
$form['memcached']['radioactivity_memcached_expiration'] = array(
'#type' => 'textfield',
'#title' => t('Memcached entry expiration time (in seconds)'),
'#description' => t('Expiration time for memcached entries used by radioactivity. This should be at least twice as long as your maximum ' . 'cron interval.'),
'#size' => 10,
'#required' => TRUE,
'#disabled' => !$memcached_ok,
'#default_value' => radioactivity_get_memcached_expiration(),
);
return system_settings_form($form);
}
function radioactivity_admin_profile_list() {
$form = array();
$decay_profiles = _radioactivity_get_decay_profiles();
$profile_rows = array();
foreach ($decay_profiles as $dpid => $decay_profile) {
$profile_rows[] = array(
'data' => array(
$dpid,
check_plain($decay_profile["label"]),
'<a href="' . url('admin/settings/radioactivity/profile_' . $dpid) . '">' . t("Edit") . '</a>',
),
);
}
$profiles_table = theme('table', array(
t('Id'),
t('Label'),
t('Actions'),
), $profile_rows);
$form['profiles_table'] = array(
'#value' => $profiles_table,
);
return $form;
}
function _radioactivity_oclassdef_to_form($oclass, $name, $def, $sources, $energy, $level = 0) {
$form = array(
'#type' => 'fieldset',
'#tree' => TRUE,
'#collapsible' => TRUE,
'#title' => t('Energy settings for @oclass', array(
'@oclass' => $name,
)),
);
$collapsed = TRUE;
if (count($sources) == 0) {
$form['no_sources'] = array(
'#type' => 'item',
'#value' => t('You must enable at least one plug-in that provides an energy source for this target class.'),
);
}
if ($level == 0 && @is_array($def['subclasses'])) {
$form[] = array(
'#type' => 'item',
'#value' => t('The default settings for #type.', array(
'#type' => $name,
)),
);
}
elseif ($level > 0) {
$form[] = array(
'#type' => 'item',
'#value' => t('Specific settings for #type. Empty field uses setting from parent.', array(
'#type' => $name,
)),
);
}
foreach ($sources as $source => $sdef) {
@($energy_value = $energy[$source]);
$form[$source] = array(
'#type' => 'textfield',
'#title' => t('Incident energy from %s', array(
'%s' => $sdef['title_placeholder'],
)),
'#default_value' => $energy_value,
);
if (strlen($energy_value) > 0) {
$collapsed = FALSE;
}
}
if (@is_array($def['subclasses'])) {
foreach ($def['subclasses'] as $subclass => $subclassdef) {
$form['subclasses'][$subclass] = _radioactivity_oclassdef_to_form($subclass, $name . ' / ' . $subclass, $subclassdef, $sources, @$energy['subclasses'][$subclass], $level + 1);
if (!$form['subclasses'][$subclass]['#collapsed']) {
$collapsed = FALSE;
}
}
}
$form['#collapsed'] = $collapsed;
return $form;
}
function radioactivity_admin_profile_form($dpid) {
$form = array();
if (!(int) $dpid) {
$dpid = -1;
}
$form[] = array(
'#type' => 'item',
'#title' => t('Profile id'),
'#value' => $dpid > 0 ? $dpid : t('Unassigned'),
);
$form['decay_profile_id'] = array(
'#type' => 'hidden',
'#value' => $dpid,
);
if ($dpid > 0) {
$decay_profiles = _radioactivity_get_decay_profiles();
$decay_profile = $decay_profiles[$dpid];
unset($decay_profiles);
}
else {
$decay_profile = array(
'half_life' => 6 * 3600,
'cut_off_energy' => 0.5,
'energy' => array(
'node' => array(
'view' => 1,
),
),
'label' => '',
'description' => '',
);
}
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Profile label'),
'#required' => TRUE,
'#description' => t('The profile label. Used in views, links, etc'),
'#default_value' => $decay_profile['label'],
);
$form['description'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#description' => t('The description of the profile.'),
'#default_value' => $decay_profile['description'],
);
$form['half_life'] = array(
'#type' => 'textfield',
'#title' => t('Half-life of the radioactivity in seconds'),
'#required' => TRUE,
'#description' => t('Determines the decay rate of the radioactivity. For exaple, if the decay rate is ' . '3600 (one hour), the radioactivity halves once an hour. If it is now 1000, it will ' . 'be 500 after an hour, 250 after two hours, and so on. The default is 6 hours.'),
'#default_value' => $decay_profile['half_life'],
);
$form['cut_off_energy'] = array(
'#type' => 'textfield',
'#title' => t('Cut-off energy'),
'#required' => TRUE,
'#description' => t('The cut-off energy. Below this energy level, the node is considered non-radioactive and ' . 'the radioactivity information will be deleted from the database. Leave 0 disable cut-off.'),
'#default_value' => $decay_profile['cut_off_energy'],
);
$radioactivity_info = radioactivity_get_radioactivity_info();
$form['energy'] = array(
'#type' => 'fieldset',
'#tree' => TRUE,
'#title' => t('Energy settings'),
);
if (count($radioactivity_info["targets"]) == 0) {
$form['energy']['no_targets'] = array(
'#type' => 'item',
'#value' => t('You must enable at least one plug-in that provides an energy target class. ' . 'Try <em>radioactivity_node</em>.'),
);
}
else {
foreach ($radioactivity_info['targets'] as $oclass => $def) {
$form['energy'][$oclass] = _radioactivity_oclassdef_to_form($oclass, $oclass, $def, $radioactivity_info['sources'][$oclass], @$decay_profile['energy'][$oclass]);
$form['energy'][$oclass]['#collapsed'] = FALSE;
}
}
$form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save decay profile'),
);
$form['buttons']['delete'] = array(
'#value' => l(t('Delete profile'), 'admin/settings/radioactivity/profile_' . $dpid . '/delete'),
);
if (!empty($_POST) && form_get_errors()) {
drupal_set_message(t('The settings have not been saved because of the errors.'), 'error');
}
return $form;
}
function _radioactivity_prune_array($energy) {
$ret = array();
foreach ($energy as $key => $value) {
if (is_array($value)) {
$value = _radioactivity_prune_array($value);
if (count($value)) {
$ret[$key] = $value;
}
}
elseif (strlen(trim($value))) {
$ret[$key] = trim($value);
}
}
return $ret;
}
function radioactivity_admin_profile_form_submit($form_id, $form) {
$dpid = (int) $form['decay_profile_id'];
if ($dpid == 0) {
drupal_set_message(t('Internal error: decay_profile_id=@dpid', array(
'@dpid' => $dpid,
)), 'error');
return FALSE;
}
$decay_profiles = _radioactivity_get_decay_profiles();
if ($dpid < 0) {
$dpids = array_keys($decay_profiles);
if (count($dpids) > 0) {
$dpid = 1 + max($dpids);
}
else {
$dpid = 1;
}
}
$decay_profile = array();
$decay_profile['label'] = $form['label'];
$decay_profile['description'] = $form['description'];
$half_life = (int) $form['half_life'];
if ($half_life <= 0) {
$half_life = 6 * 3600;
}
$decay_profile['half_life'] = $half_life;
$decay_profile['cut_off_energy'] = (double) $form['cut_off_energy'];
$decay_profile['energy'] = _radioactivity_prune_array($form['energy']);
$decay_profiles[$dpid] = $decay_profile;
_radioactivity_set_decay_profiles($decay_profiles);
drupal_set_message(t('Profile @dpid saved.', array(
'@dpid' => $dpid,
)));
}
function radioactivity_admin_delete_profile_form($dpid) {
$decay_profiles = _radioactivity_get_decay_profiles();
return confirm_form(array(
'decay_profile_id' => array(
'#type' => 'hidden',
'#value' => $dpid,
),
), t('Are you sure you want to delete radiation decay profile @label (@id)?', array(
'@label' => $decay_profiles[$dpid]['label'],
'@id' => $dpid,
)), "admin/settings/radioactivity/profile_" . $dpid, NULL, t('Delete'));
}
function radioactivity_admin_delete_profile_form_submit($form_id, $form) {
$dpid = $form['decay_profile_id'];
drupal_set_message(t("Deleted profile @id", array(
'@id' => $dpid,
)));
$decay_profiles = _radioactivity_get_decay_profiles();
unset($decay_profiles[$dpid]);
_radioactivity_set_decay_profiles($decay_profiles);
db_query("DELETE FROM {radioactivity} WHERE decay_profile=%d", $dpid);
drupal_goto("admin/settings/radioactivity");
}
function radioactivity_process_memcached_entries() {
$combined = array();
$entry_id_top = (int) dmemcache_get('entry_id_seq', 'radioactivity');
$entry_id = dmemcache_get('entry_id_processed', 'radioactivity');
if ($entry_id === FALSE) {
$entry_id = $entry_id_top;
}
if ($entry_id_top < $entry_id) {
$entry_id = 0;
}
while ($entry_id < $entry_id_top) {
++$entry_id;
$entry = dmemcache_get('entry-' . $entry_id, 'radioactivity');
if (!$entry) {
continue;
}
switch ($entry['type']) {
case 'add-energy':
++$combined[$entry['oid']][$entry['oclass']][$entry['source']];
break;
default:
}
}
foreach ($combined as $oid => $rest1) {
foreach ($rest1 as $oclass => $rest2) {
foreach ($rest2 as $source => $times) {
_radioactivity_add_energy_internal($oid, $oclass, $source, $times);
}
}
}
dmemcache_set('entry_id_processed', $entry_id, 0, 'radioactivity');
}
function radioactivity_cron() {
if (radioactivity_get_memcached_enable()) {
radioactivity_process_memcached_entries();
}
$timestamp = time();
$last_cron_timestamp = (int) variable_get('radioactivity_last_cron_timestamp', 0);
$granularity = (int) _radioactivity_get_decay_granularity();
$threshold_timestamp = $last_cron_timestamp - $last_cron_timestamp % $granularity + $granularity;
if ($timestamp < $threshold_timestamp) {
return;
}
foreach (_radioactivity_get_decay_profiles() as $dpid => $decay_profile) {
$half_life = (double) $decay_profile["half_life"];
$cut_off_energy = (double) $decay_profile["cut_off_energy"];
db_query("UPDATE {radioactivity} SET energy=energy * pow(2, (last_emission_timestamp-%d)*1.0/%f), last_emission_timestamp=%d " . "WHERE decay_profile=%d AND last_emission_timestamp<%d", $timestamp, $half_life, $timestamp, $dpid, $timestamp);
db_query("DELETE FROM {radioactivity} WHERE decay_profile=%d AND energy < %f", $dpid, $cut_off_energy);
}
variable_set('radioactivity_last_cron_timestamp', $timestamp);
}
function radioactivity_get_energy($oid, $oclass) {
$ret = array();
$oid = _radioactivity_possibly_remap_id($oid, $oclass);
$result = db_query("SELECT decay_profile, energy FROM {radioactivity} WHERE id=%d AND class='%s'", $oid, $oclass);
while ($row = db_fetch_object($result)) {
$ret[$row->decay_profile] = $row->energy;
}
return $ret;
}
function radioactivity_delete_energy($oid, $oclass) {
$oid = _radioactivity_possibly_remap_id($oid, $oclass);
db_query("DELETE FROM {radioactivity} WHERE id=%d AND class='%s'", $oid, $oclass);
return TRUE;
}
function radioactivity_get_radioactivity_array($oid, $oclass) {
$ret = array();
$energies = radioactivity_get_energy($oid, $oclass);
foreach ($energies as $dpid => $energy) {
$ret['energy'][$dpid] = (double) $energies[$dpid];
}
return $ret;
}