'. t('On this page you can configure which blocks should be provided on a per-content-type basis. If you enabled a content type, please make sure to provided a block title.') .'
'; $output .= ''. t('The Limit field allows you to provide a maximum number of nodes to be displayed for that block.') .'
'; $output .= ''. t('The Block Header Text field allows you to provide some text which can appear at the top of the block - good for explaining to the user what the block is.') .'
'; return $output; } } /** * Implementation of hook_perm(). */ function relevant_content_perm() { return array('administer relevant content'); } /** * Implementation of hook_menu(). */ function relevant_content_menu() { // Rebuild the settings when the menu gets rebuilt - this is the only way to // clear the settings cache on a module submit relevant_content_get_settings(NULL, TRUE); $items = array(); $admin_base = array( 'file' => 'relevant_content.admin.inc', 'access arguments' => array('administer relevant content'), ); $items['admin/settings/relevant_content'] = array( 'title' => 'Relevant Content', 'description' => 'Configure the sites relevant content blocks.', 'page callback' => 'relevant_content_admin', ) + $admin_base; $items['admin/settings/relevant_content/overview'] = array( 'title' => 'Overview', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ) + $admin_base; $items['admin/settings/relevant_content/add'] = array( 'title' => 'Add', 'page callback' => 'drupal_get_form', 'page arguments' => array('relevant_content_admin_block_form'), 'type' => MENU_LOCAL_TASK, ) + $admin_base; $items['admin/settings/relevant_content/%relevant_content_block/%relevant_content_admin_op'] = array( 'title callback' => 'relevant_content_admin_block_operator_title', 'title arguments' => array(3, 4), 'page callback' => 'relevant_content_admin_block_operator_callback', 'page arguments' => array(3, 4), 'type' => MENU_CALLBACK, ) + $admin_base; return $items; } /** * Relevant Content Block loader callback for the Menu API */ function relevant_content_block_load($delta) { return relevant_content_get_settings($delta); } /** * Relevant Content admin op loader callback for the Menu API * This allows a single Admin URL to serve several operations on a block */ function relevant_content_admin_op_load($op) { $ret = array('op' => $op); switch ($op) { case 'edit' : $ret['title'] = t('Edit'); $ret['callback'] = 'relevant_content_admin_block_form'; break; case 'delete' : $ret['title'] = t('Delete'); $ret['callback'] = 'relevant_content_admin_delete_confirm'; break; case 'revert' : $ret['title'] = t('Revert'); $ret['callback'] = 'relevant_content_admin_revert_confirm'; break; case 'enable' : $ret['title'] = t('Enable'); $ret['callback'] = 'relevant_content_admin_enable_confirm'; break; case 'disable' : $ret['title'] = t('Disable'); $ret['callback'] = 'relevant_content_admin_disable_confirm'; break; default : return FALSE; } return $ret; } /** * Admin block operation title callback */ function relevant_content_admin_block_operator_title($block, $op) { return t('!op Relevant Content Block - @id', array('!op' => $op['title'], '@id' => $block['id'])); } /** * Implementation of hook_block(). */ function relevant_content_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list' : $blocks = array(); $settings = relevant_content_get_settings(); if (!empty($settings)) { foreach ($settings as $block) { if ($block['status'] == RELEVANT_CONTENT_STATUS_ENABLED) { $blocks[$block['id']] = array( 'info' => t('Relevant Content: @title', array('@title' => $block['id'])), 'cache' => BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE, 'visibility' => 1, 'pages' => 'node/*', ); } } } return $blocks; case 'view' : // Load the block for the delta (id) $block = relevant_content_get_settings($delta); // If we have no block - it could be a bad delta passed in. Abort here. if (!$block) { return; } //Get the terms for the current page using a little reusable wrapper function $terms = relevant_content_get_page_terms(); //If there are no terms, not a lot of point in continuing if (empty($terms)) { return; } //Filter out the terms which are not in a selected vocabulary foreach ($terms as $key => $term) { if (isset($block['vocabs'][$term->vid])) { $terms[$key] = $term->tid; } else { unset($terms[$key]); } } //Again - if there are no terms, no need to continue! if (empty($terms)) { return; } //Create a node exclusion list - this will exclude the currently viewed node - if applicable. //This stops the currently viewed node appearing top of a list - afterall, it IS the most relevant! $exclude = array(); if ($node = menu_get_object()) { $exclude[] = $node->nid; } // Build a list of relevant nodes if ($nodes = relevant_content_get_nodes($block['types'], $terms, $exclude, $block['max_items'])) { // Return a block. // See @template_preprocess_relevant_content_block() return array( 'subject' => t('Relevant Content'), 'content' => theme('relevant_content_block', $nodes, $block), ); } break; } } /** * Handy wrapper function to find the terms for the current page */ function relevant_content_get_page_terms($node = NULL) { /** * If we have passed a node in, check if this node has taxonomy and use that. If not, try to load the terms using taxonomy_node_get_terms. * This is a rare situation, but sometimes happens if your module has a lower weight than taxonomy so on node_load, you get the node object pre-taxonomy. This happens with CCK Fields... */ if ($node) { // Use the node's terms.... if (isset($node->taxonomy) && is_array($node->taxonomy)) { $terms = $node->taxonomy; } // If we have a revision ID on the node, then we can try to load through taxonomy node get terms... elseif (isset($node->vid)) { $terms = taxonomy_node_get_terms($node); } } // If the URL is node/% then we can use Drupal 6's new menu_get_object. This method has it's risks and should be used with care. It is possible to end up in an infinit loop with one loading cycle invoking the next... elseif (arg(0) == 'node' && is_numeric(arg(1))) { $node = menu_get_object(); $terms = taxonomy_node_get_terms($node); } // Fall back to the term_cache if none of the above worked else { $terms = relevant_content_term_cache(); } // Provide a hook_relevant_content_terms where other modules can change the relevant terms if needed... drupal_alter('relevant_content_terms', $terms); return $terms; } /** * Implementation of hook_theme(). */ function relevant_content_theme($existing, $type, $theme, $path) { return array( 'relevant_content_block' => array( 'arguments' => array('nodes' => array(), 'block' => array()), 'template' => 'relevant_content_block', ), 'relevant_content_admin_overview' => array( 'arguments' => array('rows' => array()), 'template' => 'relevant_content_admin_overview', ), ); } /** * Theme preprocess function for rendering the relevant nodes into a block. * * This is provided so that an item list is the default, however a themer can * easily override this to make a teaser list or table. * * @param $vars * Contains the variables for the theme function, including: * $vars['nodes'] - An associative array of node information * $vars['block'] - the block being rendered */ function template_preprocess_relevant_content_block(&$vars) { // Get the definition of the block we are rendering $block = &$vars['block']; // Set an "is_empty" flag $vars['is_empty'] = empty($vars['nodes']); // Default to "link" type $type = 'link'; // Check tokens is enabled; it's optional. Also check we have token settings if (module_exists('token') && !empty($block['token_settings'])) { // Cleanup the token pattern // Cleanup the token pattern. See @relevant_content_filter_xss($string) // Unfortunately, we cannot 'clean up' the token pattern once due to the // bug with filter_xss on '[node:url]' which breaks the token. $token_pattern = trim($block['token_settings']); // If the token pattern is not empty, switch to tokens mode if (!empty($token_pattern)) { $type = 'tokens'; } } foreach ($vars['nodes'] as $nid => $node) { // If we're a link, default to a hyperlink - otherwise we should use tokens. switch ($type) { default : case 'link' : $node['output'] = l($node['title'], 'node/'. $node['nid']); break; case 'tokens' : $objects = array('global' => NULL, 'node' => node_load($node['nid'])); $node['output'] = token_replace_multiple($token_pattern, $objects); $node['output'] = relevant_content_filter_xss($node['output']); break; } // Build some classes for the node link row $node['classes'] = array( 'relevant-content-item', 'relevant-node-'. (int)$node['nid'], 'relevant-node-type-'. check_plain($node['type']), ); // Store the updated node over the old one $vars['nodes'][$nid] = $node; } // Build the header text - defaults to empty $vars['header'] = ''; if (!empty($block['header_text'])) { $vars['header'] = check_markup($block['header_text'], $block['header_format'], FALSE); } // Add a template suggestion; relevant_content_block-{block-id}.tpl.php $vars['template_files'][] = 'relevant_content_block-'. $block['id']; } /** * Function to get a set of nodes. * * This returns a set of nodes based on the provided type and array of term * ID's. * * @param $types * Array representing the node types * @param $terms * Array of Term ID's * @param $exclude * Array - Optional: An array of Node ID's to exclude. Useful for excluding the node you might be comparing to currently. Default: No exclusions. * @param $limit * Integer - Optional: Integer controlling the maximum number of nodes returned. Default: 5 * @param $languages * Array - Optional: An array of languages to restrict nodes to. * An empty string in the array corresponds to Language Neutral nodes. * An empty array will include all nodes regardless of language. * * @return mixed * FALSE if no result or error or an associative array with node ID's as keys and the value's as arrays of nid's, vid's, title's, type's & term match counts. */ function relevant_content_get_nodes($types, $terms, $exclude = array(), $limit = 5, $languages = array()) { // If terms or types are empty, there isn't anything to match to so not a lot of point continuing. if (empty($terms) || empty($types)) return FALSE; // Initialize the values array for the SQL $values = array(); // Define the SQL for term inclusion $term_sql = 'tn.tid IN('. db_placeholders($terms, 'int') .')'; $values = $values + array_values($terms); // Define the SQL for Node Type inclusion $types_sql = 'n.type IN ('. db_placeholders($types, 'varchar') .')'; $values = array_merge($values, array_values($types)); // Define the SQL for Node Exclusion (optional) $exclude_sql = ''; if (!empty($exclude)) { $exclude_sql = 'AND n.nid NOT IN ('. db_placeholders($exclude, 'int') .')'; $values = array_merge($values, array_values($exclude)); } // Define SQL for language restriction $language_sql = ''; if (!empty($languages)) { $language_sql = 'AND n.language IN ('. db_placeholders($languages, 'varchar') .')'; $values = array_merge($values, array_values($languages)); } // Add the result limit to the values array $values = array_merge($values, array($limit)); // Define the SQL using HereDoc $sql = <<