menu_minipanels.module in Menu Minipanels 6
Same filename and directory in other branches
Allows an administrator to specify a minipanel to be associated with a Drupal menu item. When that menu item is hovered or clicked (as per config), the minipanel content will be shown using the qTip javascript library.
Technical Overview:
Due to the Drupal 6 execution path it is necessary to do this in multiple steps: 1. During hook_init() check each menu to see if there are any menus with minipanels configured. 2. The hook_theme_registry_alter() function is used to replace the existing preprocess function for links and menu links. 3. When links or menu links are being displayed they will first be ran through the preprocess function defined in step 2, this adds a class to help identify the menu item; the final panel is rendered during this process and statically cached for later retrieval. 4. The template_preprocess_page() function loads the output previously generated and appends it to the footer.
File
menu_minipanels.moduleView source
<?php
/**
* @file
*
* Allows an administrator to specify a minipanel to be associated with a
* Drupal menu item. When that menu item is hovered or clicked (as per config),
* the minipanel content will be shown using the qTip javascript library.
*
* Technical Overview:
*
* Due to the Drupal 6 execution path it is necessary to do this in multiple
* steps:
* 1. During hook_init() check each menu to see if there are any menus with
* minipanels configured.
* 2. The hook_theme_registry_alter() function is used to replace the existing
* preprocess function for links and menu links.
* 3. When links or menu links are being displayed they will first be ran
* through the preprocess function defined in step 2, this adds a class
* to help identify the menu item; the final panel is rendered during this
* process and statically cached for later retrieval.
* 4. The template_preprocess_page() function loads the output previously
* generated and appends it to the footer.
*/
/**
* Implements hook_init().
*/
function menu_minipanels_init() {
// Optionally ignore certain pages.
if (menu_minipanels_excluded_path()) {
return;
}
// The main qTip script file should be stored in sites/all/libraries/qtip.
$qtip_path = menu_minipanels_get_qtip_path();
// If the qTip script isn't found, no point in continuing.
if ($qtip_path === FALSE) {
return;
}
// Load each of the menus that are configured for menu_minipanels. This
// happens during hook_init() due to needing to work around the execution
// path of Drupal 6.
$load_requirements = FALSE;
$enabled_menus = array();
foreach (menu_get_names() as $menu) {
if (variable_get('menu_minipanels_' . $menu . '_enabled', FALSE)) {
$enabled_menus[] = $menu;
// Loop through each level of the menu tree and see whether qTip needs to
// be loaded.
$level = 0;
while ($items = menu_navigation_links($menu, $level)) {
if (menu_minipanels_prepare_links($items)) {
$load_requirements = TRUE;
}
$level++;
}
}
}
// If the main menu is enabled and the main & secondary menus both point to
// the same menu, load the second level of that menu.
$primary_menu = variable_get('menu_primary_links_source', 'primary-links');
$secondary_menu = variable_get('menu_secondary_links_source', 'secondary-links');
if (in_array($primary_menu, $enabled_menus) && $primary_menu == $secondary_menu) {
if (menu_minipanels_prepare_links(menu_navigation_links($primary_menu, 1))) {
$load_requirements = TRUE;
}
}
// If menus are actually needed, load the required scripts & CSS.
if ($load_requirements) {
// The path to this module.
$path = drupal_get_path('module', 'menu_minipanels');
// Load the module's custom CSS.
drupal_add_css($path . '/css/menu_minipanels.css');
// This module's custom JS.
drupal_add_js($path . '/js/menu_minipanels.js');
// Optional callbacks.
if (variable_get('menu_minipanels_default_callbacks', TRUE)) {
drupal_add_js($path . '/js/menu_minipanels.callbacks.js');
}
// Load the qTip script.
drupal_add_js($qtip_path);
}
}
/**
* Check different paths to find the qTips JS file's path.
*
* @return The path to the required qTips file relative to base_path() if
* found, FALSE if the file is not found.
*/
function menu_minipanels_get_qtip_path() {
static $qtip_path = FALSE;
// Only proceed if the path wasn't compiled before.
if (empty($qtip_path)) {
$cid = 'menu_minipanels_qtip_path';
$cache = cache_get($cid);
// The path was previously cached, so just load that.
if (!empty($cache->data)) {
$qtip_path = $cache->data;
}
else {
$filename = 'jquery.qtip-1.0.0-rc3.min.js';
$module_path = drupal_get_path('module', 'menu_minipanels');
// An array of possible paths, in descending order of preference.
$possible_paths = array(
// Ideally should be stored here.
'sites/all/libraries/qtip',
// Legacy paths, including some possible incorrect ones, but the
// performance hit should be negligible.
$module_path . '/js/lib/qtip',
$module_path . '/js/lib',
$module_path . '/js/qtip',
$module_path . '/js',
$module_path . '/qtip',
$module_path,
);
// Proper Libraries API support.
if (function_exists('libraries_get_path')) {
$lib_path = libraries_get_path('qtip');
if (!empty($lib_path) && !in_array($lib_path, $possible_paths)) {
array_unshift($possible_paths, $lib_path);
}
}
// Check each of the paths.
foreach ($possible_paths as $path) {
// If the file exists, this is the one we'll use.
if (file_exists($path . '/' . $filename)) {
$qtip_path = $path . '/' . $filename;
break;
}
}
// Save the path for later.
if (!empty($qtip_path)) {
cache_set($cid, $qtip_path);
}
else {
watchdog('menu_minipanels', t('Menu Minipanels module is enabled, but the qTip library has not been downloaded. This module will not work without qTip! Please see README.txt for instructions on how to download qTip.'));
}
}
}
// Return the qTips JS file's path, or FALSE.
return $qtip_path;
}
/**
* Implements hook_help().
*/
function menu_minipanels_help($path, $arg) {
switch ($path) {
case 'admin/settings/menu_minipanels':
return '<p>' . t('The menu minipanels module integrates the very popular qTip tooltip library into Drupal. Visit the <a href="!reference_url">qTip reference</a> to learn about the various configuration options.', array(
'!reference_url' => 'http://craigsworks.com/projects/qtip/',
)) . '</p>';
}
}
/**
* Implements hook_menu().
*/
function menu_minipanels_menu() {
$items = array();
$items['admin/settings/menu_minipanels'] = array(
'title' => 'Menu MiniPanels',
'description' => 'Configure defaults for the Menu MiniPanels module.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'menu_minipanels_admin',
),
'access arguments' => array(
'administer site configuration',
),
'file' => 'menu_minipanels.admin.inc',
);
$items['admin/settings/menu_minipanels/toggle'] = array(
'title' => 'Toggle menu',
'description' => '',
'page callback' => 'menu_minipanels_menu_toggle',
'page arguments' => array(
4,
),
'access arguments' => array(
'administer site configuration',
),
'file' => 'menu_minipanels.admin.inc',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add the minipanel selector & associated settings to the menu item editor.
*/
function menu_minipanels_form_menu_edit_item_alter(&$form, $form_state) {
// Check to see whether the menu form exists.
if (isset($form['menu']['original_item']['#value']['menu_name'])) {
// Check if this menu is enabled.
if (variable_get('menu_minipanels_' . $form['menu']['original_item']['#value']['menu_name'] . '_enabled', FALSE)) {
// Load minipanels.
ctools_include('plugins', 'panels');
$panel_minis = panels_mini_load_all();
// If no Mini Panels are found, leave a message.
if (empty($panel_minis)) {
drupal_set_message(t('No Mini Panels are available, some need to be added via the <a href="!link" title="Mini Panels administrator">Mini Panels admin</a> for the Menu_MiniPanels module to work.', array(
'!link' => url('admin/build/mini-panels'),
)), 'warning');
}
else {
// Load the admin code necessary for this.
module_load_include('inc', 'menu_minipanels', 'menu_minipanels.admin');
$path = drupal_get_path('module', 'menu_minipanels');
drupal_add_js($path . '/js/menu_minipanels_admin.js');
// The 'options' element already exists, just need to tweak it.
$form['menu']['options']['#tree'] = TRUE;
$form['menu']['options']['#type'] = 'markup';
$form['menu']['options']['#weight'] = '50';
unset($form['menu']['options']['#value']['attributes']);
// Create options for select box.
$options = array(
'' => 'None',
);
foreach ($panel_minis as $panel_mini) {
// If the admin title is empty, use the minipanel name.
if (!empty($panel_mini->admin_title)) {
$title = check_plain($panel_mini->admin_title);
}
else {
$title = check_plain($panel_mini->name);
}
$options[check_plain($panel_mini->name)] = $title;
}
$item = $form['menu']['#item'];
$form['menu']['options']['minipanel'] = array(
'#type' => 'select',
'#title' => t('Menu minipanel'),
'#description' => t('Choose the minipanel to display.'),
'#default_value' => isset($item['options']['minipanel']) ? $item['options']['minipanel'] : '',
'#options' => $options,
'#required' => FALSE,
);
// Ensure the settings structure exists.
if (!isset($form['menu']['#item']['options']['menu_minipanels_hover'])) {
$form['menu']['#item']['options']['menu_minipanels_hover'] = array();
}
// Insert the custom fields.
_menu_minipanels_hover_settings_form($form['menu']['options'], $form['menu']['#item']['options']['menu_minipanels_hover'], variable_get('menu_minipanels_hover', _menu_minipanels_hover_defaults()));
_menu_minipanels_hover_settings_form($form['menu']['options'], $form['menu']['#item']['options']['menu_minipanels_hover']);
// This is prepended to the array to ensure it is executed before
// menu_edit_item_submit(). If it is executed after menu_edit_item_submit,
// then the menu_minipanels_hover array will be saved to the database
// anyway, and the intercept would be pointless.
array_unshift($form['#submit'], 'menu_minipanels_menu_edit_item_submit');
}
}
}
}
/**
* If no minipanel is set, stop minipanel settings being saved.
*/
function menu_minipanels_menu_edit_item_submit($form, &$form_state) {
if (empty($form_state['values']['menu']['options']['minipanel'])) {
unset($form_state['values']['menu']['options']['menu_minipanels_hover']);
}
else {
// Store mlid for later use in uniquely identifiying menu configs in the
// Javascript.
$form_state['values']['menu']['options']['menu_minipanels_hover']['mlid'] = $form_state['values']['menu']['mlid'];
}
}
/**
* Implementation of hook_form_FORM_ID_alter().
*/
function menu_minipanels_form_menu_edit_menu_alter(&$form, &$form_state) {
// Control whether the menu is enabled.
$menu_name = !empty($form['menu_name']['#value']) ? $form['menu_name']['#value'] : '_new_menu_';
$var_name = 'menu_minipanels_' . $menu_name . '_enabled';
$form['menu_minipanels'] = array(
'#type' => 'checkbox',
'#title' => t('Allow to be used with Menu_MiniPanels'),
'#default_value' => variable_get($var_name, FALSE),
'#description' => t('When this is enabled the Menu_MiniPanels options will be available when editing individual menu items. Disabling this for menus that don\'t need it will give a small performance gain.'),
);
$form['#submit'][] = 'menu_minipanels_form_menu_edit_menu_submit';
// Adjust the weight of the submit button so it shows at the end.
$form['submit']['#weight'] = 1000;
}
/**
* Submission callback for the menu_edit_menu form.
*/
function menu_minipanels_form_menu_edit_menu_submit($form, &$form_state) {
// When a menu is first created it doesn't have the "menu-" prefix.
$menu_name = $form['#insert'] ? 'menu-' . $form_state['values']['menu_name'] : $form_state['values']['menu_name'];
$var_name = 'menu_minipanels_' . $menu_name . '_enabled';
if (!empty($form_state['values']['menu_minipanels'])) {
variable_set($var_name, TRUE);
}
else {
variable_set($var_name, FALSE);
}
}
/**
* Implements hook_theme_registry_alter().
*
* theme_menu_item_link() is overridden because to print menus, themes and
* modules generally use menu_tree_output(), and menu_tree_output() calls
* theme_menu_item_link().
*
* This method *only* works if theme_menu_item_link is called *before* $closure
* is created in page_preprocess().
*
* If a theme calls theme('menu_tree') in a page.tpl.php for instance, this
* will not work.
*/
function menu_minipanels_theme_registry_alter(&$vars) {
$vars['menu_minipanels_theme_links_default'] = $vars['links'];
$vars['links']['function'] = 'menu_minipanels_theme_links';
$vars['menu_minipanels_theme_menu_item_link_default'] = $vars['menu_item_link'];
$vars['menu_item_link']['function'] = 'menu_minipanels_theme_menu_item_link';
}
/**
* Ensure that we capture any mini panel menus. This is run on every page load.
* See @menu_minipanels_init().
*/
function menu_minipanels_prepare_links($links) {
// Track whether the qTip code needs to be loaded.
$load_qtip = FALSE;
foreach ($links as $link) {
if (!empty($link['minipanel'])) {
$load_qtip = TRUE;
_menu_minipanels_include($link['minipanel'], $link['menu_minipanels_hover']);
}
}
return $load_qtip;
}
/**
* This replacement theme function adds minipanels support to theme_links()
* without disturbing themes that may also implement this theme function.
*
* @see menu_minipanels_theme_registry_alter().
* @see menu_minipanels_init().
* @see menu_minipanels_prepare_links().
*/
function menu_minipanels_theme_links($links, $attributes = array(
'class' => 'links',
)) {
foreach ($links as &$link) {
if (!empty($link['minipanel'])) {
$prefix = '';
if (!empty($link['attributes']['class'])) {
$prefix = $link['attributes']['class'] . ' ';
}
// Only set the class names, don't include the code. That has already
// been done earlier in @menu_minipanels_prepare_links().
$link['attributes']['class'] = $prefix . _menu_minipanels_link_class_name($link['menu_minipanels_hover']['mlid']);
}
}
return theme('menu_minipanels_theme_links_default', $links);
}
/**
* This replacement theme function adds minipanels support to theme_links()
* without disturbing themes that may also implement this theme function.
*
* @see menu_minipanels_theme_registry_alter.
*/
function menu_minipanels_theme_menu_item_link($link) {
$matches = array();
if (!empty($link['options']['minipanel'])) {
if (empty($link['localized_options'])) {
$link['localized_options'] = array(
'attributes' => array(),
);
}
$prefix = '';
if (!empty($link['localized_options']['attributes']['class'])) {
$prefix = $link['localized_options']['attributes']['class'] . ' ';
}
$link['localized_options']['attributes']['class'] = $prefix . _menu_minipanels_include($link['options']['minipanel'], $link['options']['menu_minipanels_hover']);
}
return theme('menu_minipanels_theme_menu_item_link_default', $link);
}
/**
* Implements template_preprocess_page().
*
* Output the generated MiniPanels.
*/
function menu_minipanels_preprocess_page(&$variables) {
// Optionally ignore certain pages.
if (menu_minipanels_excluded_path()) {
return;
}
// Display the previously generated output.
$variables['footer'] .= "\n" . menu_minipanels_panels();
}
/**
* Stores the generated output of all rendered minipanels. Will later be used
* by @menu_minipanels_preprocess_page to display the generated output.
*
* @param Int $mlid - the menu item id that needs the minipanel rendered.
* @param String $minipanel_name - the name of the minipanel to render.
*/
function menu_minipanels_panels($mlid = NULL, $minipanel_name = NULL) {
static $output = '';
if ($minipanel_name != NULL) {
$output .= '<div class="menu-minipanels menu-minipanel-' . $mlid . '">' . _menu_minipanels_render_panel($minipanel_name) . '</div>';
}
else {
return $output;
}
}
/**
* When a minipanel menu item is detected by our theme interception functions
* this function is used to add the appropriate configuration javascript
* and minipanel output.
*
* The javascript is added to the closure by drupal when hook_footer is called.
* The minipanel output is added to closure. See @menu_minipanels_footer
*
* @param $minipanel_name The name of the minipanel that is to show
* @param $menu_config The configuration array for qtip, as configued in menu
* item edit
*/
function _menu_minipanels_include($minipanel_name, $menu_config) {
$mlid = $menu_config['mlid'];
// The same panel/mlid may be added multiple times, if the same menu is added
// to a page more than once, i.e. the primary links, plus primary links also
// added as a block.
static $added = array();
if (!isset($added[$mlid])) {
$added[$mlid] = TRUE;
}
else {
return _menu_minipanels_link_class_name($mlid);
}
// qTip interprets the absence of the 'position' array element as 'false'.
// specifying 'false' doesn't work.
if ($menu_config['position']['target'] == 'element') {
unset($menu_config['position']['target']);
}
// qTip interprets the absence of the 'tip' array element as "don't display
// a tip". Specifying 'false' doesn't work.
if ($menu_config['style']['tip'] == 'none') {
unset($menu_config['style']['tip']);
}
// Remove blank values.
if (isset($menu_config['hide']['effect']['length']) && $menu_config['hide']['effect']['length'] == '') {
unset($menu_config['hide']['effect']['length']);
}
if (isset($menu_config['hide']['effect']['delay']) && $menu_config['hide']['effect']['delay'] == '') {
unset($menu_config['hide']['effect']['delay']);
}
if (isset($menu_config['show']['effect']['delay']) && $menu_config['show']['effect']['delay'] == '') {
unset($menu_config['show']['effect']['delay']);
}
if (isset($menu_config['show']['effect']['length']) && $menu_config['show']['effect']['length'] == '') {
unset($menu_config['show']['effect']['length']);
}
if (isset($menu_config['style']['border']['color']) && $menu_config['style']['border']['color'] == '') {
unset($menu_config['style']['border']['color']);
}
if (isset($menu_config['style']['border']['radius']) && $menu_config['style']['border']['radius'] == '') {
unset($menu_config['style']['border']['radius']);
}
if (isset($menu_config['style']['border']['width']) && $menu_config['style']['border']['width'] == '') {
unset($menu_config['style']['border']['width']);
}
if (isset($menu_config['style']['border']) && empty($menu_config['style']['border'])) {
unset($menu_config['style']['border']);
}
if (isset($menu_config['style']['width']['max']) && $menu_config['style']['width']['max'] == '') {
unset($menu_config['style']['width']['max']);
}
if (isset($menu_config['style']['width']['min']) && $menu_config['style']['width']['min'] == '') {
unset($menu_config['style']['width']['min']);
}
if (isset($menu_config['style']['width']) && empty($menu_config['style']['width'])) {
unset($menu_config['style']['width']);
}
if (isset($menu_config['position']['container']) && empty($menu_config['position']['container'])) {
unset($menu_config['position']['container']);
}
// Must have all both for this to work correctly.
if (isset($menu_config['position']['adjust']['x']) && empty($menu_config['position']['adjust']['x']) || isset($menu_config['position']['adjust']['y']) && empty($menu_config['position']['adjust']['y'])) {
unset($menu_config['position']['adjust']['x']);
unset($menu_config['position']['adjust']['y']);
}
else {
// Qtip expects an integer to compute position.
settype($menu_config['position']['adjust']['x'], 'int');
settype($menu_config['position']['adjust']['y'], 'int');
}
if (empty($menu_config['position']['adjust'])) {
unset($menu_config['position']['adjust']);
}
$settings = array(
'menuMinipanels' => array(
'panels' => array(
'panel_' . $mlid => $menu_config,
),
),
);
drupal_add_js($settings, 'setting');
// Load the necessary style JS file.
module_invoke_all('menu_minipanels_style', $menu_config);
menu_minipanels_panels($mlid, $minipanel_name);
return _menu_minipanels_link_class_name($mlid);
}
/**
* Seperated out as it may be called independently by our function that
* intercepts theme_links().
*/
function _menu_minipanels_link_class_name($minipanel_name) {
return 'menu-minipanel menu-minipanel-' . $minipanel_name;
}
/**
* Code based on panels_mini_block() in panels_mini.module.
*/
function _menu_minipanels_render_panel($minipanel_name) {
$panel_mini = panels_mini_load($minipanel_name);
if (empty($panel_mini)) {
return '';
}
ctools_include('context');
$panel_mini->context = $panel_mini->display->context = ctools_context_load_contexts($panel_mini);
$panel_mini->display->css_id = panels_mini_get_id($panel_mini->name);
return panels_render_display($panel_mini->display);
}
/**
* Check if current path should be excluded.
*/
function menu_minipanels_excluded_path() {
// By default don't exclude the page.
$exclude_path_match = FALSE;
// By default ignore the admin pages.
$exclude_paths = drupal_strtolower(variable_get('menu_minipanels_exclude_paths', "admin\nadmin/*"));
// Don't bother checking anything if the setting is empty.
if (!empty($exclude_paths)) {
// Check the current raw path first.
$exclude_path_match = drupal_match_path($_GET['q'], $exclude_paths);
// If there isn't already a patch, check for a possible alias.
if (!$exclude_path_match) {
// Get the current path.
$path = drupal_strtolower(drupal_get_path_alias($_GET['q']));
// If the path *is* different to the current raw URL, check it too.
if ($path != $_GET['q']) {
$exclude_path_match = drupal_match_path($path, $exclude_paths);
}
}
}
return $exclude_path_match;
}
Functions
Name![]() |
Description |
---|---|
menu_minipanels_excluded_path | Check if current path should be excluded. |
menu_minipanels_form_menu_edit_item_alter | Implements hook_form_FORM_ID_alter(). |
menu_minipanels_form_menu_edit_menu_alter | Implementation of hook_form_FORM_ID_alter(). |
menu_minipanels_form_menu_edit_menu_submit | Submission callback for the menu_edit_menu form. |
menu_minipanels_get_qtip_path | Check different paths to find the qTips JS file's path. |
menu_minipanels_help | Implements hook_help(). |
menu_minipanels_init | Implements hook_init(). |
menu_minipanels_menu | Implements hook_menu(). |
menu_minipanels_menu_edit_item_submit | If no minipanel is set, stop minipanel settings being saved. |
menu_minipanels_panels | Stores the generated output of all rendered minipanels. Will later be used by @menu_minipanels_preprocess_page to display the generated output. |
menu_minipanels_prepare_links | Ensure that we capture any mini panel menus. This is run on every page load. See @menu_minipanels_init(). |
menu_minipanels_preprocess_page | Implements template_preprocess_page(). |
menu_minipanels_theme_links | This replacement theme function adds minipanels support to theme_links() without disturbing themes that may also implement this theme function. |
menu_minipanels_theme_menu_item_link | This replacement theme function adds minipanels support to theme_links() without disturbing themes that may also implement this theme function. |
menu_minipanels_theme_registry_alter | Implements hook_theme_registry_alter(). |
_menu_minipanels_include | When a minipanel menu item is detected by our theme interception functions this function is used to add the appropriate configuration javascript and minipanel output. |
_menu_minipanels_link_class_name | Seperated out as it may be called independently by our function that intercepts theme_links(). |
_menu_minipanels_render_panel | Code based on panels_mini_block() in panels_mini.module. |