' . t('About') . ''; $output .= '

' . t('The Toolbar module displays links to top-level administration menu items and links from other modules at the top of the screen. For more information, see the online handbook entry for Toolbar module.', array('@toolbar' => 'http://drupal.org/documentation/modules/toolbar')) . '

'; $output .= '

' . t('Uses') . '

'; $output .= '
'; $output .= '
' . t('Displaying administrative links') . '
'; $output .= '
' . t('The Toolbar module displays a bar containing top-level administrative components across the top of the screen. Below that, the Toolbar module has a drawer section where it displays links provided by other modules, such as the core Shortcut module. The drawer can be hidden/shown by clicking on its corresponding tab.', array('@shortcuts-help' => url('admin/help/shortcut'))) . '
'; $output .= '
'; return $output; } } /** * Implements hook_permission(). */ function toolbar_permission() { return array( 'access toolbar' => array( 'title' => t('Use the administration toolbar'), ), ); } /** * Implements hook_theme(). */ function toolbar_theme($existing, $type, $theme, $path) { $items['toolbar'] = array( 'render element' => 'element', ); $items['toolbar_item'] = array( 'render element' => 'element', ); $items['toolbar_tab_wrapper'] = array( 'render element' => 'element', ); $items['toolbar_tray_wrapper'] = array( 'render element' => 'element', ); $items['toolbar_tray_heading_wrapper'] = array( 'render element' => 'element', ); return $items; } /** * Implements hook_menu(). */ function toolbar_menu() { $items['toolbar/subtrees/%'] = array( 'page callback' => 'toolbar_subtrees_jsonp', 'page arguments' => array(2), 'access callback' => '_toolbar_subtrees_access', 'access arguments' => array(2), 'type' => MENU_CALLBACK, ); return $items; } /** * Implements hook_element_info(). */ function toolbar_element_info() { $elements = array(); $elements['toolbar'] = array( '#pre_render' => array('toolbar_pre_render'), '#theme' => 'toolbar', '#attached' => array( 'library' => array( array('toolbar', 'toolbar'), ), ), // Metadata for the toolbar wrapping element. '#attributes' => array( // The id cannot be simply "toolbar" or it will clash with the simpletest // tests listing which produces a checkbox with attribute id="toolbar" 'id' => 'toolbar-administration', // The 'overlay-displace-top' class pushes the overlay down, so it appears // below the toolbar. 'class' => array('toolbar', 'overlay-displace-top'), 'role' => 'navigation', ), // Metadata for the administration bar. '#bar' => array( '#heading' => t('Toolbar items'), '#attributes' => array( 'id' => 'toolbar-bar', 'class' => array('bar', 'overlay-displace-top', 'clearfix'), ), ), ); // A toolbar item is wrapped in markup for common styling. The 'tray' // property contains a renderable array. theme_toolbar_tab() is a light // wrapper around the l() function. The contents of tray are rendered in // theme_toolbar_tab(). $elements['toolbar_item'] = array( '#pre_render' => array('toolbar_pre_render_item'), '#theme' => 'toolbar_item', '#theme_wrappers' => array('toolbar_tab_wrapper'), 'tab' => array( '#type' => 'link', '#title' => NULL, '#href' => '', ), ); return $elements; } /** * Access callback: Returns if the user has access to the rendered subtree requested by the hash. * * @see toolbar_menu(). */ function _toolbar_subtrees_access($hash) { return user_access('access toolbar') && ($hash == _toolbar_get_subtree_hash()); } /** * Page callback: Returns the rendered subtree of each top-level toolbar link. * * @see toolbar_menu(). */ function toolbar_subtrees_jsonp($hash) { _toolbar_initialize_page_cache(); $subtrees = toolbar_get_rendered_subtrees(); $response = new JsonResponse($subtrees); $response->setCallback('Drupal.toolbar.setSubtrees'); return $response; } /** * Use Drupal's page cache for toolbar/subtrees/*, even for authenticated users. * * This gets invoked after full bootstrap, so must duplicate some of what's * done by _drupal_bootstrap_page_cache(). * * @todo Replace this hack with something better integrated with DrupalKernel * once Drupal's page caching itself is properly integrated. */ function _toolbar_initialize_page_cache() { $GLOBALS['conf']['system.performance']['cache']['page']['enabled'] = TRUE; drupal_page_is_cacheable(TRUE); // If we have a cache, serve it. // @see _drupal_bootstrap_page_cache() $cache = drupal_page_get_cache(); if (is_object($cache)) { header('X-Drupal-Cache: HIT'); // Restore the metadata cached with the page. $_GET['q'] = $cache->data['path']; date_default_timezone_set(drupal_get_user_timezone()); drupal_serve_page_from_cache($cache); // We are done. exit; } // Otherwise, create a new page response (that will be cached). header('X-Drupal-Cache: MISS'); // The Expires HTTP header is the heart of the client-side HTTP caching. The // additional server-side page cache only takes effect when the client // accesses the callback URL again (e.g., after clearing the browser cache or // when force-reloading a Drupal page). $max_age = 3600 * 24 * 365; drupal_add_http_header('Expires', gmdate(DATE_RFC1123, REQUEST_TIME + $max_age)); drupal_add_http_header('Cache-Control', 'private, max-age=' . $max_age); } /** * Implements hook_page_build(). * * Add admin toolbar to the page_top region automatically. */ function toolbar_page_build(&$page) { $page['page_top']['toolbar'] = array( '#type' => 'toolbar', '#access' => user_access('access toolbar'), ); } /** * Builds the Toolbar as a structured array ready for drupal_render(). * * Since building the toolbar takes some time, it is done just prior to * rendering to ensure that it is built only if it will be displayed. * * @param array $element * A renderable array. * * @return * A renderable array. * * @see toolbar_page_build(). */ function toolbar_pre_render($element) { // Get the configured breakpoints to switch from vertical to horizontal // toolbar presentation. $breakpoints = entity_load('breakpoint_group', 'module.toolbar.toolbar'); if (!empty($breakpoints)) { $media_queries = array(); $media_queries['toolbar']['breakpoints'] = array_map( function ($object) { return $object->mediaQuery; }, $breakpoints->breakpoints ); $element['#attached']['js'][] = array( 'data' => $media_queries, 'type' => 'setting', ); } // Get toolbar items from all modules that implement hook_toolbar(). $items = module_invoke_all('toolbar'); // Allow for altering of hook_toolbar(). drupal_alter('toolbar', $items); // Sort the children. uasort($items, 'element_sort'); // Merge in the original toolbar values. $element = array_merge($element, $items); // Render the children. $element['#children'] = drupal_render_children($element); return $element; } /** * Returns HTML that wraps the administration toolbar. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties and children of * the tray. Properties used: #children, #attributes and #bar. */ function theme_toolbar(&$variables) { if (!empty($variables['element']['#children'])) { $element = $variables['element']; $trays = ''; foreach (element_children($element) as $key) { $trays .= drupal_render($element[$key]['tray']); } return '' . '' . '

' . $element['#bar']['#heading'] . '

' . $element['#children'] . '' . $trays . ''; } } /** * Provides markup for associating a tray trigger with a tray element. * * A tray is a responsive container that wraps renderable content. Trays present * content well on small and large screens alike. * * @param array $element * A renderable array. * * @return * A renderable array. */ function toolbar_pre_render_item($element) { // If tray content is present, markup the tray and its associated trigger. if (!empty($element['tray'])) { // Trays are associated with their trigger by a unique ID. $id = drupal_html_id('toolbar-tray'); $element['tab']['#tray_id'] = $id; // Provide attributes for a tray trigger. $attributes = array( 'id' => 'toolbar-tab-' . $id, 'data-toolbar-tray' => $id, 'aria-owns' => $id, 'role' => 'button', 'aria-pressed' => 'false', ); // Merge in module-provided attributes. $element['tab'] += array('#attributes' => array()); $element['tab']['#attributes'] += $attributes; $element['tab']['#attributes']['class'][] = 'trigger'; // Provide attributes for the tray theme wrapper. $attributes = array( 'id' => $id, 'data-toolbar-tray' => $id, 'aria-owned-by' => 'toolbar-tab-' . $id, ); // Merge in module-provided attributes. if (!isset($element['tray']['#wrapper_attributes'])) { $element['tray']['#wrapper_attributes'] = array(); } $element['tray']['#wrapper_attributes'] += $attributes; $element['tray']['#wrapper_attributes']['class'][] = 'tray'; $element['tray']['#wrapper_attributes']['class'][] = 'overlay-displace-top'; if (!isset($element['tray']['#theme_wrappers'])) { $element['tray']['#theme_wrappers'] = array(); } // Add the standard theme_wrapper for trays. array_unshift($element['tray']['#theme_wrappers'], 'toolbar_tray_wrapper'); // If a #heading is provided for the tray, provided a #theme_wrapper // function to append it. if (!empty($element['tray']['#heading'])) { array_unshift($element['tray']['#theme_wrappers'], 'toolbar_tray_heading_wrapper'); } } return $element; } /** * Implements template_preprocess_HOOK(). */ function template_preprocess_toolbar_tab_wrapper(&$variables) { if (!isset($variables['element']['#wrapper_attributes'])) { $variables['element']['#wrapper_attributes'] = array(); } $variables['element']['#wrapper_attributes']['class'][] = 'tab'; } /** * Returns HTML for a toolbar item. * * This theme function only renders the tab portion of the toolbar item. The * tray portion will be rendered later. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties and children of * the tray. Property used: tab. * * @see toolbar_pre_render_item(). * @see theme_toolbar(). */ function theme_toolbar_item(&$variables) { return drupal_render($variables['element']['tab']); } /** * Returns HTML for wrapping a toolbar tab. * * Toolbar tabs have a common styling and placement with the toolbar's bar area. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties and children of * the tray. Properties used: #children and #attributes. */ function theme_toolbar_tab_wrapper(&$variables) { if (!empty($variables['element']['#children'])) { $element = $variables['element']; return '' . $element['#children'] . ''; } } /** * Returns HTML for wrapping a toolbar tray. * * Used in combination with theme_toolbar_tab() to create an * association between a link tag in the administration bar and a tray. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties and children of * the tray. Properties used: #children, #toolbar_identifier and * #attributes. */ function theme_toolbar_tray_wrapper(&$variables) { if (!empty($variables['element']['#children'])) { $element = $variables['element']; return '
' . $element['#children'] . '
'; } } /** * Returns HTML for prepending a heading to a toolbar tray. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties and children of * the tray. Properties used: #children and #heading. */ function theme_toolbar_tray_heading_wrapper(&$variables) { if (!empty($variables['element']['#children'])) { $element = $variables['element']; return '

' . $element['#heading'] . '

' . $element['#children']; } } /** * Implements hook_system_info_alter(). * * Indicate that the 'page_top' region (in which the toolbar will be displayed) * is an overlay supplemental region that should be refreshed whenever its * content is updated. * * This information is provided for any module that might need to use it, not * just the core Overlay module. */ function toolbar_system_info_alter(&$info, $file, $type) { if ($type == 'theme') { $info['overlay_supplemental_regions'][] = 'page_top'; } } /** * Implements hook_toolbar(). */ function toolbar_toolbar() { $items = array(); // The 'Home' tab is a simple link, with no corresponding tray. $items['home'] = array( '#type' => 'toolbar_item', 'tab' => array( '#type' => 'link', '#title' => t('Home'), '#href' => '', '#options' => array( 'attributes' => array( 'title' => t('Home page'), 'class' => array('icon', 'icon-home'), ), ), ), '#weight' => -20, ); // Retrieve the administration menu from the database. $tree = toolbar_get_menu_tree(); // Add attributes to the links before rendering. toolbar_menu_navigation_links($tree); $menu = array( '#heading' => t('Administration menu'), 'toolbar_administration' => array( '#type' => 'container', '#attributes' => array( 'class' => array('toolbar-menu-administration'), ), 'administration_menu' => menu_tree_output($tree), ), ); // To conserve bandwidth, we only include the top-level links in the HTML. // The subtrees are included in a JSONP script, cached by the browser. Here we // add that JSONP script. We add it as an external script, because it's a // Drupal path, not a file available via a stream wrapper. // @see toolbar_subtrees_jsonp() $menu['toolbar_administration']['#attached']['js'][url('toolbar/subtrees/' . _toolbar_get_subtree_hash())] = array('type' => 'external'); // The administration element has a link that is themed to correspond to // a toolbar tray. The tray contains the full administrative menu of the site. $items['administration'] = array( '#type' => 'toolbar_item', 'tab' => array( '#type' => 'link', '#title' => t('Menu'), '#href' => 'admin', '#options' => array( 'attributes' => array( 'title' => t('Admin menu'), 'class' => array('icon', 'icon-menu'), ), ), ), 'tray' => $menu, '#weight' => -15, ); return $items; } /** * Gets only the top level items below the 'admin' path. * * @return * An array containing a menu tree of top level items below the 'admin' path. */ function toolbar_get_menu_tree() { $tree = array(); $query = entity_query('menu_link') ->condition('menu_name', 'admin') ->condition('module', 'system') ->condition('link_path', 'admin'); $result = $query->execute(); if (!empty($result)) { $admin_link = menu_link_load(reset($result)); $tree = menu_build_tree('admin', array( 'expanded' => array($admin_link['mlid']), 'min_depth' => $admin_link['depth'] + 1, 'max_depth' => $admin_link['depth'] + 1, )); } return $tree; } /** * Generates an array of links from a menu tree array. * * Based on menu_navigation_links(). Adds path based IDs and icon placeholders * to the links. * * @return * An array of links as defined above. */ function toolbar_menu_navigation_links(&$tree) { foreach ($tree as $key => $item) { // Configure sub-items. if (!empty($item['below'])) { toolbar_menu_navigation_links($tree[$key]['below']); } // Make sure we have a path specific ID in place, so we can attach icons // and behaviors to the items. $tree[$key]['link']['localized_options']['attributes'] = array( 'id' => 'toolbar-link-' . str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['link_path']), 'class' => array( 'icon', 'icon-' . strtolower(str_replace(' ', '-', $item['link']['link_title'])), ), ); } } /** * Returns the rendered subtree of each top-level toolbar link. */ function toolbar_get_rendered_subtrees() { $subtrees = array(); $tree = toolbar_get_menu_tree(); foreach ($tree as $tree_item) { $item = $tree_item['link']; if (!$item['hidden'] && $item['access']) { if ($item['has_children']) { $query = db_select('menu_links'); $query->addField('menu_links', 'mlid'); $query->condition('has_children', 1); for ($i=1; $i <= $item['depth']; $i++) { $query->condition('p' . $i, $item['p' . $i]); } $parents = $query->execute()->fetchCol(); $subtree = menu_build_tree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1)); toolbar_menu_navigation_links($subtree); $subtree = menu_tree_output($subtree); $subtree = drupal_render($subtree); } else { $subtree = ''; } $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['href']); $subtrees[$id] = $subtree; } } return $subtrees; } /** * Checks whether an item is in the active trail. * * Useful when using a menu generated by menu_tree_all_data() which does * not set the 'in_active_trail' flag on items. * * @return * TRUE when path is in the active trail, FALSE if not. * * @todo * Look at migrating to a menu system level function. */ function toolbar_in_active_trail($path) { $active_paths = &drupal_static(__FUNCTION__); // Gather active paths. if (!isset($active_paths)) { $active_paths = array(); $trail = menu_get_active_trail(); foreach ($trail as $item) { if (!empty($item['href'])) { $active_paths[] = $item['href']; } } } return in_array($path, $active_paths); } /** * Implements hook_library_info(). */ function toolbar_library_info() { $libraries['toolbar'] = array( 'title' => 'Toolbar', 'version' => VERSION, 'js' => array( drupal_get_path('module', 'toolbar') . '/js/toolbar.js' => array(), ), 'css' => array( drupal_get_path('module', 'toolbar') . '/css/toolbar.base.css', drupal_get_path('module', 'toolbar') . '/css/toolbar.theme.css', drupal_get_path('module', 'toolbar') . '/css/toolbar.icons.css', ), 'dependencies' => array( array('system', 'jquery'), array('system', 'drupal'), array('system', 'drupalSettings'), array('system', 'matchmedia'), array('system', 'jquery.once'), array('system', 'drupal.debounce'), array('toolbar', 'toolbar.menu'), ), ); $libraries['toolbar.menu'] = array( 'title' => 'Toolbar nested accordion menus.', 'version' => VERSION, 'js' => array( drupal_get_path('module', 'toolbar') . '/js/toolbar.menu.js' => array(), ), 'css' => array( drupal_get_path('module', 'toolbar') . '/css/toolbar.menu.css', ), 'dependencies' => array( array('system', 'jquery'), array('system', 'drupal'), array('system', 'jquery.once'), ), ); return $libraries; } /** * Implements hook_cache_flush(). */ function toolbar_cache_flush() { return array('toolbar'); } /** * Returns the hash of the per-user rendered toolbar subtrees. */ function _toolbar_get_subtree_hash() { global $user; $cid = $user->uid . ':' . language(LANGUAGE_TYPE_INTERFACE)->langcode; if ($cache = cache('toolbar')->get($cid)) { $hash = $cache->data; } else { $subtrees = toolbar_get_rendered_subtrees(); $hash = drupal_hash_base64(serialize($subtrees)); cache('toolbar')->set($cid, $hash); } return $hash; }