View source
<?php
require_once 'classes/Matrix.php';
require_once 'classes/Recommender.php';
define('RECOMMENDER_ADMIN_PATH', COMPUTING_MODULE_ADMIN_PATH . '/recommender');
function recommender_help($path, $args) {
$output = '';
switch ($path) {
case 'admin/help#recommender':
case RECOMMENDER_ADMIN_PATH:
$output = '<p>' . t("Please install Recommender API enabled sub-modules to make content recommendations.") . '</p>';
break;
}
return $output;
}
function recommender_menu() {
$items = array();
$items[RECOMMENDER_ADMIN_PATH] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Recommender API',
'description' => t('The admin interface to interact with Recommender API module and sub-modules.'),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'recommender_overview',
),
'access arguments' => array(
'administer recommender',
),
);
$items[RECOMMENDER_ADMIN_PATH . '/overview'] = array(
'type' => MENU_DEFAULT_LOCAL_TASK,
'title' => 'Overview',
'weight' => -10,
);
return $items;
}
function recommender_overview($form, &$form_state) {
$form['info'] = array(
'#markup' => '<p>' . t("After you configure any settings of Recommender API modules, please go to !link to submit a Recommender command. And then use either 'drush recommender-run' or the Java agent to compute recommendations offline.", array(
'!link' => l('Command', COMPUTING_MODULE_ADMIN_PATH . '/list'),
)) . '</p>',
);
$form['recommender_cron_strategy'] = array(
'#type' => 'radios',
'#title' => t('How to run cron tasks?'),
'#options' => array(
'drupal' => t('Together with Drupal Cron'),
'drush' => t('Separately with Drush'),
),
'#default_value' => variable_get('recommender_cron_strategy', 'drupal'),
);
$form['recommender_cron_strategy']['drupal']['#description'] = t('Run recommender cron tasks whenever Drupal Cron runs.');
$form['recommender_cron_strategy']['drush']['#description'] = t('Setup to run "drush recommender-cron" separately instead of running recommender cron with Drupal Cron.');
$form['warning'] = array(
'#markup' => '<p><em>' . t("WARNING: Do not use the 'Run Recommender' button below unless your dataset is small (such as the dataset in Recommender Example). Otherwise you might see PHP 'Out of Memory' error or 'Timeout' error.") . '</em></p>',
);
$form['actions'] = array(
'run' => array(
'#type' => 'submit',
'#name' => 'run',
'#value' => t('Run Recommender'),
),
);
$form['#submit'][] = 'recommender_overview_submit';
return system_settings_form($form);
}
function recommender_overview_submit($form, &$form_state) {
if ($form_state['triggering_element']['#name'] == 'run') {
recommender_run();
}
}
function recommender_run() {
$count = 0;
while ($record = computing_claim('recommender')) {
drupal_set_message(t("Processing recommender computation: !id", array(
'!id' => $record->id,
)));
recommender_process_record($record);
computing_update($record);
$count++;
}
if ($count > 0) {
drupal_set_message(t('Successfully run recommender.'));
}
else {
drupal_set_message(t('No recommender command to run.'), 'warning');
}
}
function recommender_computing_data() {
$computing_data = array(
'recommender' => recommender_fetch_data(),
);
foreach ($computing_data['recommender'] as $name => &$data) {
if (!isset($data['form callback'])) {
$data['form callback'] = 'recommender_command_form';
}
$data['command'] = $data['algorithm'];
}
return $computing_data;
}
function recommender_command_form($form, &$form_state, $data) {
$form['options'] = array(
'#type' => 'fieldset',
'#title' => t('Configure additional parameters for %command', array(
'%command' => $data['title'],
)),
'#collapsible' => FALSE,
'#collapsed' => FALSE,
'#tree' => TRUE,
);
if (isset($data['form elements callback']) && function_exists($data['form elements callback'])) {
$form['options'] = $form['options'] + call_user_func($data['form elements callback']);
}
else {
$form['options']['no_options_message'] = array(
'#markup' => t('No extra parameters defined.'),
);
}
$form['common'] = array(
'#type' => 'fieldset',
'#title' => t('Configure generic parameters'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#tree' => TRUE,
);
$form['common']['data'] = array(
'#type' => 'value',
'#value' => $data,
);
$form['common']['label'] = array(
'#type' => 'textfield',
'#title' => 'Label',
'#description' => 'The human-readable name of this command.',
'#default_value' => $data['title'],
'#required' => TRUE,
);
if (variable_get('recommender_show_database_option', FALSE)) {
$form['common']['database'] = array(
'#type' => 'select',
'#title' => t('Include database access info?'),
'#description' => t('Specifies whether to add database access info defined in settings.php into the computing record. Use this option only for development purpose when you use a remote agent to compute recommendations. Remove this option from admin interface by setting "recommender_show_database_option" to FALSE. See more in documentations.'),
'#options' => _recommender_database_options(),
'#empty_value' => '',
'#required' => FALSE,
);
}
$form['actions'] = array(
'#type' => 'actions',
'submit' => array(
'#type' => 'submit',
'#value' => t('Add Command'),
),
);
return $form;
}
function recommender_command_form_submit($form, &$form_state) {
$data = $form_state['values']['common']['data'];
$default_options = isset($data['options']) ? $data['options'] : array();
$input = array(
'algorithm' => $data['algorithm'],
'data structure' => recommender_prepare_data_structure($data['data structure']),
'options' => $default_options + (isset($form_state['values']['options']) ? $form_state['values']['options'] : array()),
);
if (variable_get('recommender_show_database_option', FALSE) && !empty($form_state['values']['common']['database'])) {
global $databases;
list($db_name, $db_target) = explode('-', $form_state['values']['common']['database']);
$input['database'] = $databases[$db_name][$db_target];
}
$command = isset($data['command']) ? $data['command'] : $data['algorithm'];
$result = computing_create($data['application'], $command, $form_state['values']['common']['label'], $input);
if ($result) {
drupal_set_message(t('Adding new recommender command successfully. Please run "drush recommender-run" or use Java agent program to execute the command.'));
}
else {
drupal_set_message(t('Cannot add new recommender command. Please inform site administrators.'), 'error');
}
$form_state['redirect'] = COMPUTING_MODULE_ADMIN_PATH;
}
function _recommender_database_options() {
global $databases;
$options = array();
foreach ($databases as $name => $target_data) {
foreach ($target_data as $target => $def) {
$options["{$name}-{$target}"] = "{$name} - {$target}";
}
}
return $options;
}
function recommender_prepare_data_structure($data_structure_override) {
$default_data_structure = array(
'preference' => array(
'name' => 'recommender_preference',
'user field' => 'uid',
'item field' => 'eid',
'score field' => 'score',
'score type' => 'number',
'timestamp field' => 'updated',
),
'user similarity' => array(
'name' => 'recommender_user_similarity',
'user1 field' => 'uid1',
'user2 field' => 'uid2',
'score field' => 'score',
'timestamp field' => 'updated',
),
'item similarity' => array(
'name' => 'recommender_item_similarity',
'item1 field' => 'eid1',
'item2 field' => 'eid2',
'score field' => 'score',
'timestamp field' => 'updated',
),
'prediction' => array(
'name' => 'recommender_prediction',
'user field' => 'uid',
'item field' => 'eid',
'score field' => 'score',
'timestamp field' => 'updated',
),
'item entity type' => 'node',
'user entity type' => 'user',
);
return drupal_array_merge_deep($default_data_structure, $data_structure_override);
}
function recommender_process_record($record) {
$mapping = array(
'user2user' => 'UserBasedRecommender',
'user2user_boolean' => 'UserBasedBooleanRecommender',
'item2item' => 'ItemBasedRecommender',
'item2item_boolean' => 'ItemBasedBooleanRecommender',
);
if (!array_key_exists($record->command, $mapping)) {
$record->status = 'FLD';
$record->message = 'Cannot identify recommender command';
$record->output = NULL;
return;
}
$recommender_class = $mapping[$record->command];
$recommender = new $recommender_class();
try {
$recommender
->initialize($record->input);
$recommender
->execute();
$result = $recommender
->finalize();
} catch (Exception $e) {
$record->status = 'FLD';
$record->message = 'Unexpected exception caught: ' . $e
->getMessage();
$record->output = NULL;
return;
}
$record->status = 'SCF';
$record->message = 'Successfully computed recommendations.';
$record->output = $result;
}
function recommender_permission() {
$permissions = array(
'administer recommender' => array(
'title' => t('Administer recommender'),
'description' => t('Administer Recommender API related modules'),
),
);
return $permissions;
}
function recommender_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'recommender'),
);
}
function recommender_fetch_data($name = NULL) {
$recommender_data = module_invoke_all('recommender_data');
drupal_alter('recommender_data', $recommender_data);
if ($name != NULL) {
if (isset($recommender_data[$name]) && is_array($recommender_data[$name])) {
$recommender_definition = $recommender_data[$name];
return $recommender_definition;
}
return FALSE;
}
else {
return $recommender_data;
}
}
function recommender_cron() {
if (variable_get('recommender_cron_strategy', 'drupal') == 'drupal') {
$queue = DrupalQueue::get('recommender_cron_process');
$queue
->createItem(time());
}
}
function recommender_cron_queue_info() {
return array(
'recommender_cron_process' => array(
'worker callback' => 'recommender_cron_process',
'time' => 600,
),
);
}
function _recommender_process_periodic_callback($timestamp, $hook_callback) {
$data = recommender_fetch_data();
$callbacks = array();
foreach ($data as $rec => $def) {
if (@$def[$hook_callback] && function_exists($def[$hook_callback])) {
$callbacks[$def[$hook_callback]] = $def[$hook_callback];
}
}
shuffle($callbacks);
foreach ($callbacks as $callback) {
$callback($timestamp);
}
}
function recommender_cron_process($timestamp) {
_recommender_process_periodic_callback($timestamp, 'cron callback');
}
function recommender_upkeep_process($timestamp) {
_recommender_process_periodic_callback($timestamp, 'upkeep callback');
}