crumbs.module in Crumbs, the Breadcrumbs suite 7
Same filename and directory in other branches
Provides an API for building breadcrumbs.
File
crumbs.moduleView source
<?php
/**
* @file
* Provides an API for building breadcrumbs.
*/
// Register the module-provided autoloader if xautoload is missing.
if (!module_exists('xautoload')) {
spl_autoload_register('_crumbs_autoload');
}
/**
* Implements hook_permission().
*/
function crumbs_permission() {
return array(
'administer crumbs' => array(
'title' => t('Administer Crumbs'),
),
);
}
/**
* Implements hook_menu().
*/
function crumbs_menu() {
$items = array();
$items['admin/structure/crumbs'] = array(
'title' => 'Crumbs',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'crumbs_admin_form',
),
'access arguments' => array(
'administer crumbs',
),
'file' => 'admin/crumbs.admin.inc',
);
$items['admin/structure/crumbs/settings'] = array(
'title' => 'Settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
$items['admin/structure/crumbs/node-parent'] = array(
'title' => 'Node parent',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'crumbs_admin_node_parent_form',
),
'access arguments' => array(
'administer crumbs',
),
'file' => 'admin/crumbs.node_parent.inc',
'weight' => 1,
'type' => MENU_LOCAL_TASK,
);
$items['admin/structure/crumbs/debug'] = array(
'title' => 'Debug',
'page callback' => 'crumbs_debug_page',
'access arguments' => array(
'administer crumbs',
),
'file' => 'admin/crumbs.debug.inc',
'weight' => 2,
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implements hook_theme().
*/
function crumbs_theme($existing, $type, $theme, $path) {
return array(
'crumbs_breadcrumb_link' => array(
'item' => NULL,
),
'crumbs_breadcrumb_current_page' => array(
'item' => NULL,
),
);
}
/**
* Default theme implementation for theme('crumbs_breadcrumb_link').
*/
function theme_crumbs_breadcrumb_link(array $item) {
$options = isset($item['localized_options']) ? $item['localized_options'] : array();
return l($item['title'], $item['href'], $options);
}
/**
* Default theme implementation for theme('crumbs_breadcrumb_current_page').
*/
function theme_crumbs_breadcrumb_current_page(array $item) {
return check_plain($item['title']);
}
/**
* Implements hook_block_info()
*/
function crumbs_block_info() {
return array(
'breadcrumb' => array(
'info' => t('Breadcrumb (Crumbs)'),
'cache' => DRUPAL_NO_CACHE,
),
);
}
/**
* Implements hook_block_view()
*/
function crumbs_block_view($delta = '') {
$block = array();
switch ($delta) {
case 'breadcrumb':
$breadcrumb_data = crumbs_get_breadcrumb_data();
if ($breadcrumb_data) {
$block['content'] = $breadcrumb_data['html'];
}
break;
}
return $block;
}
/**
* Implements hook_preprocess_page().
*/
function crumbs_preprocess_page(&$vars) {
$vars['crumbs_trail'] = array();
$vars['breadcrumb'] = '';
$breadcrumb_data = crumbs_get_breadcrumb_data();
if ($breadcrumb_data) {
$vars['crumbs_trail'] = $breadcrumb_data['trail'];
$vars['breadcrumb'] = $breadcrumb_data['html'];
}
}
/**
* Implements hook_themekey_properties().
*/
function crumbs_themekey_properties() {
$attributes = array();
$attributes['crumbs:trail_paths'] = array(
'description' => t('Crumbs trail paths'),
'validator' => '',
'page cache' => THEMEKEY_PAGECACHE_SUPPORTED,
);
$maps = array();
$maps[] = array(
'src' => 'drupal:get_q',
'dst' => 'crumbs:trail_paths',
'callback' => '_crumbs_themekey_path2trailpaths',
);
return array(
'attributes' => $attributes,
'maps' => $maps,
);
}
/**
* Callback for themekey integration.
*/
function _crumbs_themekey_path2trailpaths($path) {
$trail = crumbs_get_trail($path);
$paths = array();
foreach ($trail as $k => $item) {
$paths[$item['alias']] = TRUE;
$paths[$item['route']] = TRUE;
$paths[$k] = TRUE;
}
return array_keys($paths);
}
/**
* Returns the breadcrumb data for the current page.
*
* Gets the menu trail for the current page, and then uses it to build the
* breadcrumb. Each link is themed separately, and then the links are passed
* to theme('breadcrumb'), which returns the final rendered breadcrumb.
*
* Note: If the existing Drupal-provided breadcrumb is empty, then Crumbs
* makes no effort to calculate its own, since it means that a module has
* intentionally removed it.
*
* Breadcrumbs with one item are also ignored, to prevent the breadcrumb
* from being shown on the frontpage.
*
* @return
* An associative array with the following keys:
* - trail: An array containing the menu trail of the current page.
* - items: An array containing the built breadcrumb.
* - html: The rendered breadcrumb received from theme('breadcrumb').
* or FALSE if the breadcrumb could not be determined.
*/
function crumbs_get_breadcrumb_data() {
static $data;
if (!isset($data)) {
$existing_breadcrumb = drupal_get_breadcrumb();
// If the existing breadcrumb is empty, that means a module has
// intentionally removed it. Honor that, and stop here.
if (empty($existing_breadcrumb)) {
$data = FALSE;
return $data;
}
$show_current_page = variable_get('crumbs_show_current_page', FALSE);
$trail = crumbs_get_trail();
if ($show_current_page) {
// Do not show the breadcrumb if it contains only 1 item. This prevents
// the breadcrumb from showing up on the frontpage.
if (count($trail) == 1) {
$data = FALSE;
return $data;
}
}
else {
// The current page should not be shown, remove it from the trail so that
// the plugins don't need to waste time loading its title.
array_pop($trail);
}
$breadcrumb_items = crumbs_build_breadcrumb($trail);
$router_item = crumbs_get_router_item($_GET['q']);
// Allow modules to alter the breadcrumb, if possible, as that is much
// faster than rebuilding an entirely new active trail.
drupal_alter('menu_breadcrumb', $breadcrumb_items, $router_item);
$links = array();
$last_item = end($breadcrumb_items);
foreach ($breadcrumb_items as $i => $item) {
if ($show_current_page && $item == $last_item) {
// The current page should be styled differently (no link).
$links[$i] = theme('crumbs_breadcrumb_current_page', $item);
}
else {
$links[$i] = theme('crumbs_breadcrumb_link', $item);
}
}
$html = theme('breadcrumb', array(
'breadcrumb' => $links,
'crumbs_breadcrumb_items' => $breadcrumb_items,
'crumbs_trail' => $trail,
));
$data = array(
'trail' => $trail,
'items' => $breadcrumb_items,
'html' => $html,
);
}
return $data;
}
/**
* Returns the trail for the provided path.
*
* @param $path
* The path for which the trail is built. If NULL, the url of the
* current page is assumed.
*
* @return
* An associative array containing the trail, with the paths as the keys, and
* the router items (as received from crumbs_get_router_item()) as the values.
*
* @see crumbs_TrailFinder
*/
function crumbs_get_trail($path = NULL) {
static $trails = array();
if (!isset($path)) {
$path = $_GET['q'];
}
$path = drupal_get_normal_path($path);
if (!isset($trails[$path])) {
$finder = crumbs_get_trail_finder();
$trails[$path] = $finder
->buildTrail($path);
}
return $trails[$path];
}
/**
* Builds the breadcrumb based on the provided trail.
*
* Each breadcrumb item is a router item taken from the trail, with
* two additional/updated keys:
* - title: The title of the breadcrumb item as received from a plugin.
* - localized_options: An array of options passed to l() if needed.
*
* @param $trail
* The trail provided by crumbs_get_trail().
*
* @return
* An array of breadcrumb items.
*
* @see crumbs_BreadcrumbBuilder
*/
function crumbs_build_breadcrumb(array $trail) {
$plugin_engine = crumbs_get_plugin_engine();
$breadcrumb_builder = new crumbs_BreadcrumbBuilder($plugin_engine);
return $breadcrumb_builder
->buildBreadcrumb($trail);
}
/**
* Loads and instantiates the trail finder.
*
* @return
* An instance of crumbs_TrailFinder.
*/
function crumbs_get_trail_finder() {
static $trail_finder;
if (!isset($trail_finder)) {
$parent_finder = crumbs_get_parent_finder();
$trail_finder = new crumbs_TrailFinder($parent_finder);
}
return $trail_finder;
}
/**
* Loads and instantiates the parent finder.
*
* @return
* An instance of crumbs_ParentFinder.
*/
function crumbs_get_parent_finder() {
static $parent_finder;
if (!isset($parent_finder)) {
$plugin_engine = crumbs_get_plugin_engine();
$parent_finder = new crumbs_ParentFinder($plugin_engine);
}
return $parent_finder;
}
/**
* Loads and instantiates the plugin engine.
*
* @return
* An instance of crumbs_PluginEngine.
*/
function crumbs_get_plugin_engine() {
static $plugin_engine;
if (!isset($plugin_engine)) {
list($plugins, $disabled_keys) = crumbs_get_plugins();
$weights = crumbs_get_weights();
foreach ($disabled_keys as $key => $disabled) {
if (!isset($weights[$key])) {
$weights[$key] = FALSE;
}
}
$plugin_engine = new crumbs_PluginEngine($plugins, $weights);
}
return $plugin_engine;
}
/**
* Returns an array of loaded plugins and disabled plugin keys.
*
* @return
* An array containing two arrays:
* - An array of loaded plugins keyed by plugin key.
* - An array of disabled plugin keys.
*/
function crumbs_get_plugins() {
static $plugins, $disabled_keys;
if (!isset($plugins)) {
$modules = array(
'blog',
'comment',
'crumbs',
'entityreference',
'menu',
'path',
'taxonomy',
'forum',
'entityreference_prepopulate',
);
// Include Crumbs-provided plugins.
foreach ($modules as $module) {
if (module_exists($module)) {
module_load_include('inc', 'crumbs', 'plugins/crumbs.' . $module);
}
}
// Organic groups is a special case,
// because 7.x-2.x behaves different from 7.x-1.x.
if (module_exists('og')) {
if (function_exists('og_get_group')) {
// We are using the og-7.x-1.x branch.
module_load_include('inc', 'crumbs', 'plugins/crumbs.og');
}
else {
// We are using the og-7.x-2.x branch.
module_load_include('inc', 'crumbs', 'plugins/crumbs.og.2');
}
}
$plugins = array();
$disabled_keys = array();
$api = new crumbs_InjectedAPI_hookCrumbsPlugins($plugins, $disabled_keys);
foreach (module_implements('crumbs_plugins') as $module) {
$function = $module . '_crumbs_plugins';
$api
->setModule($module);
$function($api);
}
}
return array(
$plugins,
$disabled_keys,
);
}
/**
* Returns an array of plugin keys and their weights.
*
* Make sure to distinguish the weight 0 from FALSE, which denotes a disabled
* plugin.
*
* The "*" plugin key is a wildcard that triggers inheritance (runs all
* implicitly enabled plugins that aren't in the provided list).
* It always has the biggest weight.
*
* @return
* An array of plugin keys and their weights, sorted by weight.
*/
function crumbs_get_weights() {
$weights = variable_get('crumbs_weights', array(
'crumbs.home_title' => 0,
));
asort($weights);
if (!isset($weights['*'])) {
$weights['*'] = count($weights);
}
return $weights;
}
/**
* Returns a router item.
*
* This is a wrapper around menu_get_item() that sets additional keys
* (route, link_path, alias, fragments).
*
* @param $path
* The path for which the corresponding router item is returned.
* For example, node/5.
*
* @return
* The router item.
*/
function crumbs_get_router_item($path) {
$normalpath = drupal_get_normal_path($path);
try {
$item = menu_get_item($normalpath);
} catch (Exception $e) {
// Some modules throw an exception, if a path has unloadable arguments.
// We don't care, because we don't actually load this page.
return NULL;
}
// Some additional keys.
if (!empty($item) && is_array($item)) {
// 'route' is a less ambiguous name for a router path than 'path'.
$item['route'] = $item['path'];
// 'href' sounds more like it had already run through url().
$item['link_path'] = $normalpath;
$item['alias'] = drupal_get_path_alias($normalpath);
$item['fragments'] = explode('/', $normalpath);
if (!isset($item['localized_options'])) {
$item['localized_options'] = array();
}
return $item;
}
}
/**
* Chop off path fragments until we find a valid path.
*
* @param $path
* Starting path or alias
* @param $depth
* Max number of fragments we try to chop off. -1 means no limit.
*/
function crumbs_reduce_path($path, $depth = -1) {
$fragments = explode('/', $path);
while (count($fragments) > 1 && $depth !== 0) {
array_pop($fragments);
$parent_path = implode('/', $fragments);
$parent_item = crumbs_get_router_item($parent_path);
if ($parent_item && $parent_item['href'] === $parent_item['link_path']) {
return $parent_item['link_path'];
}
--$depth;
}
}
/**
* This function has exactly one possible input value for
* each possible return value, except the return value FALSE.
*
* @param $router_path :string
* The router path can contain any character, but will typically
* have a format like "node/%/edit".
* @return :string or FALSE
* A string that can be used as a method suffix,
* or FALSE, where that is not possible.
* The route "node/%/edit" will resolve as "node_x_edit".
*/
function crumbs_build_method_suffix($router_path) {
$method_suffix = strtolower($router_path);
$method_suffix = preg_replace('#[^a-z0-9\\%]#', '_', $method_suffix);
$method_suffix = strtr($method_suffix, array(
'%' => 'x',
));
$reverse = strtr($method_suffix, array(
'_' => '/',
));
$reverse = preg_replace(array(
'#/x/#',
'#/x$#',
), array(
'/%/',
'/%',
), $reverse);
// we need to do this two time to catch things like "/x/x/x/x".
$reverse = strtr($reverse, array(
'/x/' => '/%/',
));
if ($reverse === $router_path) {
return $method_suffix;
}
return FALSE;
}
/**
* Measures time between two runs and displays it in a human-readable format.
*
* @param $label
* String used to identify the measured time.
*
* @param
* A string with the measurement, or NULL if the function was run for the
* first time.
*/
function crumbs_benchmark($label = NULL) {
static $previous_time;
$current_time = microtime(TRUE);
if (isset($previous_time) && isset($label)) {
$str = 'Duration: ' . number_format(1000 * ($current_time - $previous_time), 3) . ' ms for ' . $label;
}
$previous_time = $current_time;
return isset($str) ? $str : NULL;
}
/**
* Crumbs autoloader.
*
* Takes the class name, strips the "crumbs_" prefix, converts underscores
* to directory separators.
*
* For example, crumbs_InjectedAPI_describeMonoPlugin will be loaded
* from lib/InjectedAPI/describeMonoPlugin.php.
*
* @param $class
* The name of the class to load.
*/
function _crumbs_autoload($class) {
if (preg_match('#^crumbs_(.*)$#', $class, $m)) {
$path = strtr($m[1], '_', '/');
module_load_include('php', 'crumbs', "lib/{$path}");
}
}
/**
* Clean tokens so they are URL friendly.
* Taken directly from pathauto_clean_token_values()
*
* @param $replacements
* An array of token replacements that need to be "cleaned" for use in the URL.
* @param $data
* An array of objects used to generate the replacements.
* @param $options
* An array of options used to generate the replacements.
*/
function crumbs_clean_token_values(&$replacements, $data = array(), $options = array()) {
foreach ($replacements as $token => &$value) {
// Only clean non-path tokens.
if (!preg_match('/(path|alias|url|url-brief)\\]$/', $token)) {
// We use a simple regex instead of pathauto_cleanstring().
$value = preg_replace('#[^a-z0-9/]+#', '-', strtolower($value));
}
}
}
// ============================================ interfaces =====================
/**
* Interface to be used internally by the plugin engine.
*/
interface crumbs_PluginOperationInterface_find {
function invoke($plugin, $plugin_key, $weight_keeper);
}
/**
* Interface to be used internally by the plugin engine.
*/
interface crumbs_PluginOperationInterface_alter {
function invoke($plugin, $plugin_key);
}
// -----------------------------------------------------------------------------
/**
* Interface for plugin objects registered with hook_crumbs_plugins().
*/
interface crumbs_MonoPlugin {
/**
* @param $api :crumbs_InjectedAPI_describeMonoPlugin
* Injected API object, with methods that allows the plugin to further
* describe itself.
*
* @return
* As an alternative to the API object's methods, the plugin can simply
* return a string label.
*/
function describe($api);
}
// -----------------------------------------------------------------------------
/**
* Interface for plugin objects registered with hook_crumbs_plugins().
*
*/
interface crumbs_MultiPlugin {
/**
* @param $api :crumbs_InjectedAPI_describeMultiPlugin
* Injected API object, with methods that allow the plugin to further
* describe itself.
* The plugin is supposed to tell Crumbs about all possible rule keys, and
* can give a label and a description for each.
*
* @return
* As an alternative to the API object's methods, the plugin can simply
* return a key-value array, where the keys are the available rules, and the
* values are their respective labels.
*/
function describe($api);
}
Functions
Name | Description |
---|---|
crumbs_benchmark | Measures time between two runs and displays it in a human-readable format. |
crumbs_block_info | Implements hook_block_info() |
crumbs_block_view | Implements hook_block_view() |
crumbs_build_breadcrumb | Builds the breadcrumb based on the provided trail. |
crumbs_build_method_suffix | This function has exactly one possible input value for each possible return value, except the return value FALSE. |
crumbs_clean_token_values | Clean tokens so they are URL friendly. Taken directly from pathauto_clean_token_values() |
crumbs_get_breadcrumb_data | Returns the breadcrumb data for the current page. |
crumbs_get_parent_finder | Loads and instantiates the parent finder. |
crumbs_get_plugins | Returns an array of loaded plugins and disabled plugin keys. |
crumbs_get_plugin_engine | Loads and instantiates the plugin engine. |
crumbs_get_router_item | Returns a router item. |
crumbs_get_trail | Returns the trail for the provided path. |
crumbs_get_trail_finder | Loads and instantiates the trail finder. |
crumbs_get_weights | Returns an array of plugin keys and their weights. |
crumbs_menu | Implements hook_menu(). |
crumbs_permission | Implements hook_permission(). |
crumbs_preprocess_page | Implements hook_preprocess_page(). |
crumbs_reduce_path | Chop off path fragments until we find a valid path. |
crumbs_theme | Implements hook_theme(). |
crumbs_themekey_properties | Implements hook_themekey_properties(). |
theme_crumbs_breadcrumb_current_page | Default theme implementation for theme('crumbs_breadcrumb_current_page'). |
theme_crumbs_breadcrumb_link | Default theme implementation for theme('crumbs_breadcrumb_link'). |
_crumbs_autoload | Crumbs autoloader. |
_crumbs_themekey_path2trailpaths | Callback for themekey integration. |
Interfaces
Name | Description |
---|---|
crumbs_MonoPlugin | Interface for plugin objects registered with hook_crumbs_plugins(). |
crumbs_MultiPlugin | Interface for plugin objects registered with hook_crumbs_plugins(). |
crumbs_PluginOperationInterface_alter | Interface to be used internally by the plugin engine. |
crumbs_PluginOperationInterface_find | Interface to be used internally by the plugin engine. |