' . 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 links 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' => 'toolbar', 'template' => 'toolbar', ); 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; } /** * 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( '#pre_render' => array('toolbar_pre_render'), '#access' => user_access('access toolbar'), ); } /** * Provides pre_render function for the toolbar. * * 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. * * @see toolbar_page_build(). */ function toolbar_pre_render($toolbar) { $toolbar = array_merge($toolbar, toolbar_view()); return $toolbar; } /** * Implements template_preprocess_HOOK(). */ function template_preprocess_toolbar(&$variables) { // Metadata for the toolbar wrapping element. $variables['attributes'] = new Attribute(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' )); // Provide a convenience variable for the themed tabs. $variables['toolbar']['tabs']['#attributes']['class'][] = 'overlay-displace-top'; $variables['tabs'] = $variables['toolbar']['tabs']; // Place the tabs in a top-level variable so that attribute information // can be associated with each one. foreach ($variables['toolbar']['trays'] as $key => $tray) { // Create tray heading text. Prefer the provided heading text if it exists. $heading = isset($tray['#heading']) ? $tray['#heading'] : t('@group actions', array('@group' => $key)); $variables['trays'][$key] = array( 'heading' => $heading, 'content' => $variables['toolbar']['trays'][$key], 'attributes' => new Attribute(array( 'id' => 'toolbar-tray-' . $key, 'class' => array('tray', 'overlay-displace-top', $key), 'data-toolbar-tray' => $key, 'aria-owned-by' => 'toolbar-tab-' . $key, ) ), ); } } /** * 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( 'tab' => array( 'title' => t('Home'), 'href' => '', 'html' => FALSE, '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( 'toolbar_administration' => array( '#type' => 'container', '#attributes' => array( 'class' => array('toolbar-menu',), ), 'administration_menu' => menu_tree_output($tree), ), '#heading' => t('Administration menu'), ); // 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'); $items['administration'] = array( 'tab' => array( 'title' => t('Menu'), 'href' => 'admin', 'html' => FALSE, 'attributes' => array( 'title' => t('Admin menu'), 'class' => array('icon', 'icon-menu'), ), ), 'tray' => $menu, 'weight' => -15, ); return $items; } /** * Builds the Toolbar as a structured array ready for drupal_render(). * * @return * A renderable arrray, with two children: * - 'tabs': an array of links, rendered by theme('links'). * - 'trays': an array of render elements displayed when the corresponding tab * is activated. */ function toolbar_view() { $build = array('#theme' => 'toolbar'); $build['#attached']['library'][] = array('toolbar', 'toolbar'); // Get the configured breakpoint 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); $build['#attached']['js'][] = array( 'data' => $media_queries, 'type' => 'setting', ); // // Load the breakpoints for toolbar. foreach ($breakpoints->breakpoints as $key => $breakpoint) { $build['#attached']['js'][0]['data']['toolbar']['breakpoints'][$key] = $breakpoint->mediaQuery; } } // Get toolbar items from all modules that implement hook_toolbar() or // hook_toolbar_alter(). $toolbar_groups = module_invoke_all('toolbar'); drupal_alter('toolbar', $toolbar_groups); uasort($toolbar_groups, 'drupal_sort_weight'); // Build the tabs and trays from the toolbar groups. $build['trays'] = array(); $build['tabs'] = array( '#theme' => 'links', '#links' => array(), '#attributes' => array( 'class' => array('bar', 'clearfix'), ), '#heading' => array('text' => t('Toolbar'), 'level' => 'h2', 'class' => 'element-invisible'), ); $tab_defaults = array( 'title' => '', 'href' => '', 'html' => FALSE, 'attributes' => new Attribute(), ); foreach ($toolbar_groups as $group => $items) { if ($tab = $items['tab']) { // Merge in the defaults. $tab += $tab_defaults; } // Register a tray if one is associated with this tab. if (!empty($items['tray'])) { // Provide an id, a data attribute linking each tab to the corresponding // tray and aria information. $tab['attributes']['id'] = 'toolbar-tab-' . $group; $tab['attributes']['data-toolbar-tray'] = $group; $tab['attributes']['aria-owns'] = 'toolbar-tray-' . $group; $tab['attributes']['role'] = 'button'; $tab['attributes']['aria-pressed'] = 'false'; if (array_key_exists($group, $build['trays'])) { array_merge($build['trays'][$group], $items['tray']); } else { // Assign the tray to the build. $build['trays'][$group] = $items['tray']; } } // Assign the tabs to the build. $build['tabs']['#links'][$group] = $tab; } return $build; } /** * 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(); $admin_link = db_query('SELECT * FROM {menu_links} WHERE menu_name = :menu_name AND module = :module AND link_path = :path', array(':menu_name' => 'admin', ':module' => 'system', ':path' => 'admin'))->fetchAssoc(); if ($admin_link) { $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']['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; }