array( 'cache' => TRUE, 'use hooks' => TRUE, 'classes' => array('handler'), ), ); } /** * Implementation of hook_context_plugins(). * * This is a ctools plugins hook. */ function context_context_plugins() { module_load_include('inc', 'context', 'context.plugins'); return _context_context_plugins(); } /** * Implementation of hook_context_registry(). */ function context_context_registry() { module_load_include('inc', 'context', 'context.plugins'); return _context_context_registry(); } /** * Implementation of hook_init(). */ function context_init() { if ($plugin = context_get_plugin('condition', 'sitewide')) { $plugin->execute(1); } if ($plugin = context_get_plugin('condition', 'path')) { $plugin->execute(); } if ($plugin = context_get_plugin('condition', 'query_string')) { $plugin->execute(); } if ($plugin = context_get_plugin('condition', 'language')) { global $language; $plugin->execute($language->language); } if ($plugin = context_get_plugin('condition', 'user')) { global $user; $plugin->execute($user); } } /** * Implementation of hook_preprocess_menu_link(). * * This allows menus that are not primary/secondary menus to get * the "active" class assigned to them. This assumes they are using * theme('menu_link') for the menu rendering to html. */ function context_preprocess_menu_link(&$variables) { if ($contexts = context_active_contexts()) { foreach ($contexts as $context) { if (isset($context->reactions['menu'])) { // In context module < v3.2 the url was a string. In version 3.3+ this is // an array of urls. Implement interims BC layer. // // Examples: // - OLD < v3.2 context reaction structure: // array('menu' => 'taxonomy/term/1') // // - NEW 3.3+ context reaction structure: // array( // 'menu' => array( // 0 => 'navigation:taxonomy/term/1' // 1 => 'foo-menu:taxonomy/term/1' // ) // ) $reactions_menu = is_array($context->reactions['menu']) ? array_values($context->reactions['menu']) : array($context->reactions['menu']); // Get everything after the first ':' character (if found) as the url to // match against element '#href'. $urls = array(); foreach ($reactions_menu as $url) { if (strpos($url, ':') !== FALSE) { // Get unique menu name 'navigation' from 'navigation:taxonomy/term/1' $reaction_menu = explode(':', $url); $path = $reaction_menu[1]; $urls[$path] = $reaction_menu[0]; } else { // BC layer for menu contexts that have not re-saved. This is for // urls like 'taxonomy/term/1'. We need to add a fake menu key // 'bc-context-menu-layer' or the BC link get's removed by // array_intersect below. // // @TODO: Remove BC layer in 4.x $urls[$url] = 'context-reaction-menu-bc-layer'; } } // Filter urls by the menu name of the current link. The link reaction // can be configured per menu link in specific menus and the contect // reaction should not applied to other menus with the same menu link. $menu_name = $variables['element']['#original_link']['menu_name']; $menu_paths = array_intersect($urls, array($menu_name, 'context-reaction-menu-bc-layer')); $reaction_menu_paths = array_keys($menu_paths); // - If menu href and context reaction menu url match, add the 'active' // css class to the link of this menu. // - Do not add class twice on current page. if (in_array($variables['element']['#href'], $reaction_menu_paths) && $variables['element']['#href'] != $_GET['q']) { // Initialize classes array if not set. if (!isset($variables['element']['#localized_options']['attributes']['class'])) { $variables['element']['#localized_options']['attributes']['class'] = array(); } // Do not add the 'active' class twice in views tabs. if (!in_array('active', $variables['element']['#localized_options']['attributes']['class'])) { $variables['element']['#localized_options']['attributes']['class'][] = 'active'; } } } } } } /** * Load & crud functions ============================================== */ /** * Context loader. * * @param $name * The name for this context object. * * @return * Returns a fully-loaded context definition. */ function context_load($name = NULL, $reset = FALSE) { ctools_include('export'); static $contexts; static $altered; if (!isset($contexts) || $reset) { $contexts = $altered = array(); if (!$reset && $contexts = context_cache_get('context')) { // Nothing here. } else { if ($reset) { ctools_export_load_object_reset('context'); } $contexts = ctools_export_load_object('context', 'all'); context_cache_set('context', $contexts); } } if (isset($name)) { // Allow other modules to alter the value just before it's returned. if (isset($contexts[$name]) && !isset($altered[$name])) { $altered[$name] = TRUE; drupal_alter('context_load', $contexts[$name]); } return isset($contexts[$name]) ? $contexts[$name] : FALSE; } return $contexts; } /** * Inserts or updates a context object into the database. * @TODO: should probably return the new cid on success -- make sure * this doesn't break any checks elsewhere. * * @param $context * The context object to be inserted. * * @return * Returns true on success, false on failure. */ function context_save($context) { $existing = context_load($context->name, TRUE); if ($existing && ($existing->export_type & EXPORT_IN_DATABASE)) { drupal_write_record('context', $context, 'name'); } else { drupal_write_record('context', $context); } context_load(NULL, TRUE); context_invalidate_cache(); return TRUE; } /** * Deletes an existing context. * * @param $context * The context object to be deleted. * * @return * Returns true on success, false on failure. */ function context_delete($context) { if (isset($context->name) && ($context->export_type & EXPORT_IN_DATABASE)) { db_query("DELETE FROM {context} WHERE name = :name", array(':name' => $context->name)); context_invalidate_cache(); return TRUE; } return FALSE; } /** * Exports the specified context. */ function context_export($context, $indent = '') { $output = ctools_export_object('context', $context, $indent); $translatables = array(); foreach (array('description', 'tag') as $key) { if (!empty($context->{$key})) { $translatables[] = $context->{$key}; } } $translatables = array_filter(array_unique($translatables)); if (!empty($translatables)) { $output .= "\n"; $output .= "{$indent}// Translatables\n"; $output .= "{$indent}// Included for use with string extractors like potx.\n"; sort($translatables); foreach ($translatables as $string) { $output .= "{$indent}t(" . ctools_var_export($string) . ");\n"; } } return $output; } /** * API FUNCTIONS ====================================================== */ /** * CTools list callback for bulk export. */ function context_context_list() { $contexts = context_load(NULL, TRUE); $list = array(); foreach ($contexts as $context) { $list[$context->name] = $context->name; } return $list; } /** * Wrapper around cache_get() to make it easier for context to pull different * datastores from a single cache row. */ function context_cache_get($key, $reset = FALSE) { static $cache; if (!isset($cache) || $reset) { $cache = cache_get('context', 'cache'); $cache = $cache ? $cache->data : array(); } return !empty($cache[$key]) ? $cache[$key] : FALSE; } /** * Wrapper around cache_set() to make it easier for context to write different * datastores to a single cache row. */ function context_cache_set($key, $value) { $cache = cache_get('context', 'cache'); $cache = $cache ? $cache->data : array(); $cache[$key] = $value; cache_set('context', $cache); } /** * Wrapper around context_load() that only returns enabled contexts. */ function context_enabled_contexts($reset = FALSE) { $enabled = array(); foreach (context_load(NULL, $reset) as $context) { if (empty($context->disabled)) { $enabled[$context->name] = $context; } } return $enabled; } /** * Queue or activate contexts that have met the specified condition. * * @param $context * The context object to queue or activate. * @param $condition * String. Name for the condition that has been met. * @param $reset * Reset flag for the queue static cache. */ function context_condition_met($context, $condition, $reset = FALSE) { static $queue; if (!isset($queue) || $reset) { $queue = array(); } if (!context_isset('context', $context->name)) { // Context is using AND mode. Queue it. if (isset($context->condition_mode) && $context->condition_mode == CONTEXT_CONDITION_MODE_AND) { $queue[$context->name][$condition] = $condition; // If all conditions have been met. set the context. if (!array_diff(array_keys($context->conditions), $queue[$context->name])) { context_set('context', $context->name, $context); } } // Context is using OR mode. Set it. else { context_set('context', $context->name, $context); } } } /** * Loads any active contexts with associated reactions. This should be run * at a late stage of the page load to ensure that relevant contexts have been set. */ function context_active_contexts() { $contexts = context_get('context'); return !empty($contexts) && is_array($contexts) ? $contexts : array(); } /** * Loads an associative array of conditions => context identifiers to allow * contexts to be set by different conditions. */ function context_condition_map($reset = FALSE) { static $condition_map; if (!isset($condition_map) || $reset) { if (!$reset && $cache = context_cache_get('condition_map')) { $condition_map = $cache; } else { $condition_map = array(); foreach (array_keys(context_conditions()) as $condition) { if ($plugin = context_get_plugin('condition', $condition)) { foreach (context_enabled_contexts() as $context) { $values = $plugin->fetch_from_context($context, 'values'); foreach ($values as $value) { if (!isset($condition_map[$condition][$value])) { $condition_map[$condition][$value] = array(); } $condition_map[$condition][$value][] = $context->name; } } } } context_cache_set('condition_map', $condition_map); } } return $condition_map; } /** * Invalidates all context caches(). * @TODO: Update to use a CTools API function for clearing plugin caches * when/if it becomes available. */ function context_invalidate_cache() { cache_clear_all('context', 'cache', TRUE); cache_clear_all('plugins:context', 'cache', TRUE); } /** * Implementation of hook_flush_caches(). */ function context_flush_caches() { context_invalidate_cache(); } /** * Recursive helper function to determine whether an array and its * children are entirely empty. */ function context_empty($element) { $empty = TRUE; if (is_array($element)) { foreach ($element as $child) { $empty = $empty && context_empty($child); } } else { $empty = $empty && !isset($element); } return $empty; } /** * Get a plugin handler. */ function context_get_plugin($type = 'condition', $key, $reset = FALSE) { static $cache = array(); if (!isset($cache[$type][$key]) || $reset) { switch ($type) { case 'condition': $registry = context_conditions(); break; case 'reaction': $registry = context_reactions(); break; } if (isset($registry[$key], $registry[$key]['plugin'])) { ctools_include('plugins'); $info = $registry[$key]; $plugins = ctools_get_plugins('context', 'plugins'); if (isset($plugins[$info['plugin']]) && $class = ctools_plugin_get_class($plugins[$info['plugin']], 'handler')) { // Check that class exists until CTools & registry issues are resolved. if (class_exists($class)) { $cache[$type][$key] = new $class($key, $info); } } } } return isset($cache[$type][$key]) ? $cache[$type][$key] : FALSE; } /** * Get all context conditions. */ function context_conditions($reset = FALSE) { return _context_registry('conditions', $reset); } /** * Get all context reactions. */ function context_reactions($reset = FALSE) { return _context_registry('reactions', $reset); } /** * Retrieves & caches the context registry. */ function _context_registry($key = NULL, $reset = FALSE) { static $registry; if (!isset($registry) || $reset) { if (!$reset && $cache = context_cache_get('registry')) { $registry = $cache; } else { $registry = module_invoke_all('context_registry'); drupal_alter('context_registry', $registry); context_cache_set('registry', $registry); } } if (isset($key)) { return isset($registry[$key]) ? $registry[$key] : array(); } return $registry; } /** * hook_block_view_alter - if the context editor block is on this page, * ensure that all blocks have some content so that empty blocks are * not dropped */ function context_block_view_alter(&$data, $block) { if (context_isset('context_ui', 'context_ui_editor_present') && empty($data['content'])) { $data['content']['#markup'] = "
" . t('This block appears empty when displayed on this page.') . "
"; $data['context_block_hidden'] = TRUE; } } /** * implement hook_page_alter() * * used for region context */ function context_page_alter(&$page) { if ($plugin = context_get_plugin('reaction', 'region')) { $plugin->execute($page); } } /** * hook_block_view_alter - if the context editor block is on this page, * ensure that all blocks have some content so that empty blocks are * not dropped */ function context_preprocess_block(&$vars) { if (isset($vars['block']->context_block_hidden)) { $vars['classes_array'][] = 'context-block-hidden'; $vars['classes_array'][] = 'context-block-empty'; } }