'Recently read content', 'description' => 'Tracks the history of recently read content by each user.', 'page callback' => 'drupal_get_form', 'page arguments' => array('recently_read_settings'), 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, ); return $items; } /** * Implementation of hook_theme(). */ function recently_read_theme() { return array( 'recently_read_item' => array( 'arguments' => array('item' => NULL) ), 'recently_read_item_list' => array( 'arguments' => array('item' => NULL) ), ); } /** * Implementation of hook_block(). */ function recently_read_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': // each enabled content type has its own block $types = node_get_types(); $enabled = variable_get('recently_read_node_types', array('page', 'story')); $blocks = array(); foreach ($enabled as $key) { $blocks[$key]['info'] = t('Recently read - @type', array('@type' => $types[$key]->name)); $blocks[$key]['cache'] = BLOCK_NO_CACHE; } return $blocks; case 'configure': // allow user to customize the length of a list for each node type $max_entries = variable_get('recently_read_max_entries', 10); $max_count = variable_get("recently_read_max_length", array('page' => 10, 'story' => 10)); $form['items_count'] = array( '#type' => 'textfield', '#title' => t('Maximum number of links to display in the block'), '#description' => t('Enter the positive integer value, less or equal to %limit.', array('%limit' => $max_entries) ), '#default_value' => (key_exists($delta, $max_count)) ? $max_count[$delta] : $max_entries, ); return $form; case 'save': // save configuration settings $max_entries = variable_get('recently_read_max_entries', 10); $value = max(1, min($edit['items_count'], $max_entries)); $max_count = variable_get('recently_read_max_length', array('page' => 10, 'story' => 10)); $max_count[$delta] = $value; variable_set('recently_read_max_length', $max_count); return; case 'view': // disable caching of entire page if recently read block is being displayed recently_read_disable_page_cache(); // view block containing links to recently visited nodes global $user; $max_entries = variable_get('recently_read_max_entries', 10); $max_count = variable_get('recently_read_max_length', array('page' => 10, 'story' => 10)); isset($max_count[$delta]) ? $limit = $max_count[$delta] : $limit = $max_entries; $items = recently_read_get_read_items(array($delta), $user->uid, $limit); $types = node_get_types(); return array( 'subject' => t('Recently read - @type', array('@type' => $types[$delta]->name)), 'content' => theme('recently_read_item_list', $items) ); } } function recently_read_exit() { global $user; drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH); // track history for authenticated user if ((arg(0) == 'node') && is_numeric(arg(1))) { // && arg(2) == '') { $nid = arg(1); // get node type $type = db_result(db_query('SELECT type FROM {node} WHERE nid = %d', $nid)); // track history for authenticated user if (recently_read_is_enabled($type) && $user->uid) { $record = new stdClass(); $record->nid = $nid; $record->type = $type; $record->uid = $user->uid; $record->timestamp = time(); $views = db_result(db_query( 'SELECT views FROM {recently_read_nodes} WHERE nid = %d AND uid = %d AND type = "%s"', $nid, $user->uid, $type )); // a node has been viewed before, make an update if (db_affected_rows()) { $record->views = $views + 1; drupal_write_record('recently_read_nodes', $record, array('nid', 'uid')); } // a node has not been viewed before, add new row else { $record->views = 1; drupal_write_record('recently_read_nodes', $record); } } // track history for anonymous user if (recently_read_is_enabled($type) && !$user->uid && variable_get('recently_read_anonymous_enabled', FALSE)) { $key = "recently_read-$type"; if (!isset($_SESSION[$key])) { $_SESSION[$key] = array(); } // remove previous entry, if present unset($_SESSION[$key][$nid]); // add new entry at the beginning of array $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid)); $entry = array( 'nid' => $nid, 'title' => $title, 'type' => $type, 'timestamp' => time() ); $_SESSION[$key] = array($nid => $entry) + $_SESSION[$key]; while (count($_SESSION[$key]) > variable_get('recently_read_max_entries', 10)) { array_pop($_SESSION[$key]); } } // remove old entries $nids = array(); $result = db_query("SELECT nid FROM {recently_read_nodes} WHERE uid = %d and type = '%s' ORDER BY timestamp DESC LIMIT %d,1000", $user->uid, $type, variable_get('recently_read_max_entries', 10)+1 ); while ($nid = db_result($result)) { $nids[] = $nid; }; if (count($nids)) { $placeholders = implode(',', array_fill(0, count($nids), "%d")); db_query("DELETE FROM {recently_read_nodes} WHERE uid = %d AND nid IN($placeholders)", array_merge((array) $user->uid, $nids) ); } } } /* * Form builder; Configure recently read settings. */ function recently_read_settings() { $types = node_get_types(); $options = array(); foreach ($types as $key => $type) { $options[$key] = $type->name; } $form['node_types'] = array( '#type' => 'checkboxes', '#title' => t('Enable history tracking of the following content'), '#description' => t('Select which content types will be tracked.'), '#default_value' => variable_get('recently_read_node_types', array('page', 'story')), '#options' => $options ); $form['anonymous_enabled'] = array( '#type' => 'checkbox', '#title' => t('Enable history tracking for anonymous users'), '#description' => t('If disabled, user must be logged to view recently read blocks.'), '#default_value' => variable_get('recently_read_anonymous_enabled', FALSE) ); $form['max_entries'] = array( '#type' => 'textfield', '#title' => t('Recently read list length'), '#description' => 'Provide the maximum number of entires stored for each read content type (per user) in the database.', '#default_value' => variable_get('recently_read_max_entries', 10), '#required' => TRUE ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save configuration') ); return $form; } function recently_read_settings_validate($form, &$form_state) { $max = $form_state['values']['max_entries']; if (!is_numeric($max) || $max < 1) { form_set_error('max_entries', t('%field must be a positive integer value.', array('%field' => $form['max_entries']['#title']) )); } } function recently_read_settings_submit($form, &$form_state) { $selected = array(); foreach ($form_state['values']['node_types'] as $value) { if ($value) { $selected[] = $value; } } variable_set('recently_read_node_types', $selected); variable_set('recently_read_max_entries', $form_state['values']['max_entries']); variable_set('recently_read_anonymous_enabled', $form_state['values']['anonymous_enabled']); drupal_set_message(t('The configuration options have been saved.')); } /* * Function that checks if a specific node type history tracking has been enabled */ function recently_read_is_enabled($node_type) { $enabled_types = variable_get('recently_read_node_types', array('page', 'story')); return in_array($node_type, $enabled_types); } /* * Get a list of recently read items by node types and for a specific user, * sorted by read date. * * @param $node_types * An array of node types. * * @param $user_id * Id of a user whose list is returned. * * @param $limit * Number of items to return (zero to return all) * * @return * An array of recently read items. Each item is an array. * Properties of the item: nid, title, type, timestamp */ function recently_read_get_read_items($node_types, $user_id, $limit = 0) { // normalize arguments if (!is_array($node_types)) { $node_types = array($node_types); } if ($limit == 0) { $limit = variable_get('recently_read_max_entries', 10) * count($node_types); } // get history from _SESSION variable if anonymous if ($user_id==0 && variable_get('recently_read_anonymous_enabled', FALSE)) { $items = array(); foreach ($node_types as $node_type) { $key = "recently_read-$node_type"; if (isset($_SESSION[$key]) && is_array($_SESSION[$key])) { $items = $items + $_SESSION[$key]; } } usort($items, '_recently_read_sort_fcn'); $items = array_slice($items, 0, $limit); } // get history from database if authenticated if ($user_id > 0) { // make a list of links to recently read nodes which are published $placeholders = db_placeholders($node_types, 'text'); // prepare args for sql query $args = array($user_id); $args = array_merge($args, $node_types); $args[] = $limit; $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, n.type, rr.timestamp FROM {node} n INNER JOIN {recently_read_nodes} rr ON n.nid = rr.nid WHERE rr.uid = %d AND n.status = 1 AND n.type IN($placeholders) ORDER BY rr.timestamp DESC LIMIT 0, %d"), $args ); $items = array(); while ($row = db_fetch_array($result)) { $items[] = $row; } } return $items; } /* * Disables page caching. Call this function if you want to display recently read content (i.e. block). */ function recently_read_disable_page_cache() { $GLOBALS['conf']['cache'] = FALSE; } /* * Return a themed recently read item. * * @param $item * An object containing the properties of the item. * Properties used: nid, title, type, timestamp * @return * A themed HTML string containing a link to the recently read item. */ function theme_recently_read_item($item) { return l($item['title'], 'node/' . $item['nid']); } /** * Return a themed list of recently read items. * * @param $items * An array of recently read items to be displayed in the list. * @return * A string containing the list output. * */ function theme_recently_read_item_list($items) { if (count($items)==0) { return t('Nothing has been read yet.'); } // theme each individual item on the list foreach ($items as &$item) { $item = theme('recently_read_item', $item); } // theme the list return theme('item_list', $items); } /* * Compare function for sorting recently read items */ function _recently_read_sort_fcn($a, $b) { $delta = $b['timestamp'] - $a['timestamp']; if ($delta > 0) return 1; if ($delta < 0) return -1; return 0; }