array(), ); return $items; } /** * Implementation of hook_theme_registry_alter(). */ function context_theme_registry_alter(&$theme_registry) { // Push theme_page() through a context_preprocess to provide // context-sensitive menus and variables. if (!in_array('context_preprocess_page', $theme_registry['page']['preprocess functions'])) { $theme_registry['page']['preprocess functions'][] = 'context_preprocess_page'; } // Reroute theme_blocks() through context_blocks to determine block // visibility by context. unset($theme_registry['blocks']['preprocess functions']); $theme_registry['blocks']['function'] = 'context_blocks'; } /** * Implementation of hook_context_conditions(). * * Allows modules to integrate with context and provide their native * objects as options for setting a context definition. The * hook should return an array of items keyed on the object "type" * (e.g. "node", "user", etc.) with key-value pairs corresponding to * a FormAPI element array with some restrictions and additional info. * * '#title': Required. The title of the object / form option. * '#type': Required. The FormAPI element type to use. Currently only * 'select', 'checkboxes', 'radio', and 'textfield' are allowed. * '#description': Optional. Help text to be displayed on the form. * '#options': Required. A key-value array of options. They key will be * stored and passed to context_set_by_condition(), so the integrating module * should use a unique (within its namespace) / usable identifier. */ function context_context_conditions() { $items = array(); // Content Types $nodetypes = array(); foreach (node_get_types() as $type) { $nodetypes[$type->type] = t(drupal_ucfirst($type->name)); } $items['node'] = array( '#title' => t('Node pages'), '#description' => t('Set this context when viewing a node page or using the add/edit form of one of these content types.'), '#options' => $nodetypes, '#type' => 'checkboxes', ); // User $items['user'] = array( '#title' => t('User pages'), '#description' => t('Set this context when a user with selected role(s) is viewed'), '#options' => user_roles(true), '#type' => 'checkboxes', ); // Book if (module_exists('book')) { $options = array(); foreach(book_get_books() as $book) { $options[$book['menu_name']] = $book['title']; } $items['book'] = array( '#title' => t('Book'), '#description' => t('Set this context when a node in the selected book is viewed.'), '#options' => $options, '#type' => 'checkboxes', ); } // Sitewide context $items['sitewide'] = array( '#title' => t('Sitewide context'), '#type' => 'radios', '#options' => array(0 => t('False'), 1 => t('True')), '#description' => t('Should this context always be set? If true, this context will be active across your entire site.'), ); // Path $items['path'] = array( '#title' => t('Path'), '#description' => t('Set this context when any of the paths above match the beginning or all of the page path. Put each path on a separate line.'), '#type' => 'textarea', '#element_validate' => array('context_condition_text_validate'), ); return $items; } /** * Element validate handler for setter textareas and texfields. * Will process and convert a string to an array of matchable * elements by splitting on an appropriate delimiter ("\n" for * textareas and "," for textfields). */ function context_condition_text_validate($element, &$form_state) { if (!empty($element['#value']) && in_array($element['#type'], array('textfield', 'textarea'))) { switch ($element['#type']) { case 'textfield': $delimiter = ','; break; case 'textarea': $delimiter = "\n"; break; } $items = $element['#value']; $items = explode($delimiter, $items); if (!empty($items)) { $values = array(); foreach ($items as $k => $v) { $v = trim($v); if (!empty($v)) { $values[$v] = TRUE; } } $id = end($element['#parents']); $form_state['values']['items'][$id] = $values; } } } /** * Implementation of hook_context_reactions(). * * Allows modules to integrate with context and provide options for * responding when a context has been set. The hook should return an * array of items keyed on the "type" of getter (e.g. "menu", "theme", * etc.) with key-value pairs corresponding to a FormAPI element array * with some restrictions and additional info. * * The getter element array provided differs from the setter array in * that it may store a tree of values (i.e. where #tree => true). The * values will be stored in a serialized array in the database. * * '#title': Required. The title of the object / form option. * '#type': Required. The FormAPI element type to use. Currently only * 'select', 'checkboxes', 'radio', and 'textfield' are allowed. * '#description': Optional. Help text to be displayed on the form. * '#options': Required. A key-value array of options. They key will be * stored and passed to context_set_by_condition(), so the integrating module * should use a unique (within its namespace) / usable identifier. */ function context_context_reactions() { $items = array(); // Menu if (module_exists('menu')) { $menus = menu_parent_options(array_reverse(menu_get_menus()), NULL); $root_menus = array(); foreach ($menus as $key => $name) { $id = explode(':', $key); if ($id[1] == '0') { $root_menus[$id[0]] = check_plain($name); } else { $link = menu_link_load($id[1]); $root_menu = $root_menus[$id[0]]; $menus[$root_menu][$link['link_path']] = $name; } unset($menus[$key]); } array_unshift($menus, "-- ". t('None') ." --"); $items['menu'] = array( '#title' => t('Active menu'), '#description' => t('Display the selected menu item as active when this context is set. To use this feature, you must use theme_context_links() to theme your links. Please see README.txt for more information.'), '#options' => $menus, '#type' => 'select', ); } // Implements context-based theme improvements $items['theme_section'] = array( '#tree' => true, '#title' => t('Theme variables'), 'title' => array( '#title' => t('Section title'), '#description' => t('Provides this text as a $section_title variable for display in page.tpl.php when this context is active.'), '#type' => 'textfield', '#maxlength' => 255, ), 'subtitle' => array( '#title' => t('Section subtitle'), '#description' => t('Provides this text as a $section_subtitle variable for display in page.tpl.php when this context is active.'), '#type' => 'textfield', '#maxlength' => 255, ), 'class' => array( '#title' => t('Section class'), '#description' => t('Provides this text as an additional body class (in $body_classes in page.tpl.php) when this section is active. Note that there may only be one active section class at once.'), '#type' => 'textfield', '#maxlength' => 64, ), ); // Implements context-based region disabling $theme_key = variable_get('theme_default', 'garland'); $regions = system_region_list($theme_key); $items['theme_regiontoggle'] = array( '#title' => t('Disabled regions'), '#type' => 'checkboxes', '#options' => $regions, ); return $items; } /** * Implementation of hook_nodeapi(). */ function context_nodeapi(&$node, $op, $teaser, $page) { if ($op == 'view' && $page && menu_get_object() === $node) { // Implementation of context_set_by_condition for node. context_set_by_condition('node', $node->type); // Implementation of context_set_by_condition for book. if (module_exists('book') && isset($node->book)) { if ($node->book['menu_name']) { context_set_by_condition('book', $node->book['menu_name']); } } } } /** * Implementation of hook_form_alter(). */ function context_form_alter(&$form, $form_state, $form_id) { if (isset($form['#node']) && arg(0) != 'admin') { // Prevent this from firing on admin pages... damn form driven apis... context_set_by_condition('node', $form['#node']->type); } if ($form_id == 'system_modules') { context_invalidate_cache(); } } /** * Implementation of hook_form_alter() for comment_form. */ function context_form_comment_form_alter(&$form, $form_state) { if ($nid = $form['nid']['#value']) { $node = node_load($nid); context_set_by_condition('node', $node->type); } } /** * Implementation of hook_user(). */ function context_user($op, &$edit, &$account, $category = NULL) { if ($op == 'view' && !empty($account->roles)) { foreach (array_keys($account->roles) as $rid) { context_set_by_condition('user', $rid); } } } /** * BLOCK HANDLING ===================================================== */ /** * This override of theme_blocks() is called because of an alter of the * theme registry. See context_theme_registry_alter(). */ function context_blocks($region) { $output = ""; if ($list = context_block_list($region)) { foreach ($list as $key => $block) { $output .= theme("block", $block); } } // Add any content assigned to this region through drupal_set_content() calls. $output .= drupal_get_content($region); return $output; } /** * An alternative version of block_list() that provides any context enabled blocks. */ function context_block_list($region) { static $blocks; static $context_blocks; static $disabled_regions; if (!isset($context_blocks)) { $blocks = array(); $context_blocks = array(); $disabled_regions = array(); // Store all active context blocks when first called $context_blocks = array(); foreach (context_active_values('block') as $block) { $block = (object) $block; $context_blocks["{$block->module}-{$block->delta}"] = $block; } $disabled_regions = context_active_values('theme_regiontoggle'); global $user, $theme_key; $rids = array_keys($user->roles); // This query is identical to the one in block_list(), but status = 1 is excluded to // retain blocks that may be enabled via context. $result = db_query(db_rewrite_sql("SELECT DISTINCT b.* FROM {blocks} b LEFT JOIN {blocks_roles} r ON b.module = r.module AND b.delta = r.delta WHERE b.theme = '%s' AND (r.rid IN (". db_placeholders($rids) .") OR r.rid IS NULL) ORDER BY b.region, b.weight, b.module", 'b', 'bid'), array_merge(array($theme_key), $rids)); while ($block = db_fetch_object($result)) { $bid = "{$block->module}-{$block->delta}"; // If block is not enabled & not enabled via context, skip it if (!empty($context_blocks[$bid])) { $block->region = $context_blocks[$bid]->region; $block->weight = $context_blocks[$bid]->weight; $enabled = TRUE; } else if (!$block->status) { continue; } // Initialize region key if (!isset($blocks[$block->region])) { $blocks[$block->region] = array(); } // Use the user's block visibility setting, if necessary if ($block->custom != 0) { if ($user->uid && isset($user->block[$block->module][$block->delta])) { $enabled = $user->block[$block->module][$block->delta]; } else { $enabled = ($block->custom == 1); } } else { $enabled = TRUE; } // Match path if necessary if ($block->pages) { if ($block->visibility < 2) { $path = drupal_get_path_alias($_GET['q']); // Compare with the internal and path alias (if any). $page_match = drupal_match_path($path, $block->pages); if ($path != $_GET['q']) { $page_match = $page_match || drupal_match_path($_GET['q'], $block->pages); } // When $block->visibility has a value of 0, the block is displayed on // all pages except those listed in $block->pages. When set to 1, it // is displayed only on those pages listed in $block->pages. $page_match = !($block->visibility xor $page_match); } else { $page_match = drupal_eval($block->pages); } } else { $page_match = TRUE; } $block->enabled = $enabled; $block->page_match = $page_match; $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block; } // Sort blocks -- we must do this here since blocks provided via // context may have overridden or altered weights. foreach ($blocks as $r => $dummy) { uasort($blocks[$r], '_context_block_sort'); } } // Kill blocks in disabled regions foreach (array_keys($disabled_regions) as $r) { unset($blocks[$r]); } // ================================================================== // The block rendering code below is identical to block_list(). // ================================================================== // Create an empty array if there were no entries if (!isset($blocks[$region])) { $blocks[$region] = array(); } foreach ($blocks[$region] as $key => $block) { // Render the block content if it has not been created already. if (!isset($block->content)) { // Erase the block from the static array - we'll put it back if it has content. unset($blocks[$region][$key]); if ($block->enabled && $block->page_match) { // Check the current throttle status and see if block should be displayed // based on server load. if (!($block->throttle && (module_invoke('throttle', 'status') > 0))) { // Try fetching the block from cache. Block caching is not compatible with // node_access modules. We also preserve the submission of forms in blocks, // by fetching from cache only if the request method is 'GET'. if (!count(module_implements('node_grants')) && $_SERVER['REQUEST_METHOD'] == 'GET' && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) { $array = $cache->data; } else { $array = module_invoke($block->module, 'block', 'view', $block->delta); if (isset($cid)) { cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY); } } if (isset($array) && is_array($array)) { foreach ($array as $k => $v) { $block->$k = $v; } } } if (isset($block->content) && $block->content) { // Override default block title if a custom display title is present. if ($block->title) { // Check plain here to allow module generated titles to keep any markup. $block->subject = $block->title == '' ? '' : check_plain($block->title); } if (!isset($block->subject)) { $block->subject = ''; } $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block; } } } } return $blocks[$region]; } /** * Helper function to sort blocks. */ function _context_block_sort($a, $b) { return ($a->weight - $b->weight); } /** * THEME FUNCTIONS & RELATED ========================================== */ /** * Generates a themed set of links for node types associated with * the current active contexts. */ function theme_context_links($links) { $output = ''; foreach ($links as $link) { $options = array_merge($link, array('attributes' => array('class' => 'button'))); if (!empty($link['custom'])) { $output .= l($link['title'], $link['href'], $options); } else { $output .= l('+ '. t('Add !type', array('!type' => $link['title'])), $link['href'], $options); } } return $output; } /** * Generates an array of links (suitable for use with theme_links) * to the node forms of types associated with current active contexts. */ function context_links($reset = false) { static $links; if (!$links || $reset) { $links = array(); $active_types = context_active_values('node'); if (!empty($active_types)) { // Collect types $types = node_get_types(); // Iterate over active contexts foreach ($active_types as $type) { $type_url = str_replace('_', '-', $type); $add_url = 'node/add/'. $type_url; if (isset($types[$type]) && strpos($_GET['q'], $add_url) === FALSE && node_access('create', $type)) { $links[$type_url] = array('title' => $types[$type]->name, 'href' => $add_url); } } } drupal_alter('context_links', $links); } return $links; } /** * Implementation of preprocess_page(). */ function context_preprocess_page(&$vars) { $info = context_active_values('theme_section'); $vars['section_title'] = !empty($info['title']) ? $info['title'] : ''; $vars['section_subtitle'] = !empty($info['subtitle']) ? $info['subtitle'] : ''; $vars['body_classes'] .= !empty($info['class']) ? ' '. $info['class'] : ''; // If primary + secondary links are pointed at the same menu, provide // contextual trailing by default. if (variable_get('menu_primary_links_source', 'primary-links') == variable_get('menu_secondary_links_source', 'secondary-links')) { $vars['primary_links'] = context_menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links')); $vars['secondary_links'] = context_menu_navigation_links(variable_get('menu_secondary_links_source', 'secondary-links'), 1); } $vars['primary_links'] = context_menu_set_active($vars['primary_links']); $vars['secondary_links'] = context_menu_set_active($vars['secondary_links']); if ($context_links = context_links()) { $vars['context_links'] = theme('context_links', $context_links); } } /** * Iterates through a provided links array for use with theme_links() * (e.g. from menu_primary_links()) and provides an active class for * any items that have a path that matches an active context. * * @param $links * An array of links. * @param $reset * A boolean flag for resetting the static cache. * * @return * A modified links array. */ function context_menu_set_active($links = array(), $reset = FALSE) { $active_paths = context_active_values('menu'); // Iterate through the provided links and build a new set of links // that includes active classes $new_links = array(); if (!empty($links)) { foreach ($links as $key => $link) { if (!empty($link['href']) && in_array($link['href'], $active_paths)) { if (isset($links['attributes']['class'])) { $link['attributes']['class'] .= ' active'; } else { $link['attributes']['class'] = 'active'; } if (strpos(' active', $key) === FALSE) { $new_links[$key .' active'] = $link; } } else { $new_links[$key] = $link; } } } return $new_links; } /** * Wrapper around menu_navigation_links() that gives themers the option of * building navigation links based on an active context trail. */ function context_menu_navigation_links($menu_name, $level = 0) { // Retrieve original path so we can repair it after our hack. $original_path = $_GET['q']; // Retrieve the first active menu path found. $active_paths = context_active_values('menu'); if (!empty($active_paths)) { $path = current($active_paths); if (menu_get_item($path)) { menu_set_active_item($path); } } // Build the links requested $links = menu_navigation_links($menu_name, $level); // Repair and get out menu_set_active_item($original_path); return $links; }