You are here

footermap.module in footermap: a footer site map 6

File

footermap.module
View source
<?php

/*
 * @file
 * This module queries the menu for pages and makes a dynamic
 * sitemap at the bottom of the page.
 *
 * copyright Matthew Radcliffe, Kosada Inc.
 *
 */

/*
 * Implementation of hook_help
 */
function footermap_help($path, $arg) {
  switch ($path) {

    //TODO: add target link to block system help page
    case 'admin/help#footermap':
      return '<p>' . t('Displays a dynamic, flexible sitemap at the bottom of a page via the Drupal block system. This is routinely used as a way of providing quick links at the bottom of the page. It is not advised to generate a full site map at the footer without caching.') . '</p>';
      break;
  }
}

/*
 * Implementation of hook_menu
 */
function footermap_menu() {
  $items = array();
  $items['admin/settings/footermap'] = array(
    'title' => 'footermap',
    'description' => 'Configure settings that footermap will use to dynamically generate the sitemap.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'footermap_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/*
 * Footermap settings
 * @return system_settings_callback form
 */
function footermap_settings() {
  $form = array();
  $form['recurse_limit'] = array(
    '#type' => 'textfield',
    '#title' => t('Recurse Limit'),
    '#description' => t('Limit the footermap recursion function to <em>N</em> recursions. The default is 0, unlimited. This is useful if you have a deep hierarchy of child menu items that you do not want to display in the footermap.'),
    '#size' => 3,
    '#max_length' => 3,
    '#default_value' => variable_get('recurse_limit', 0),
  );
  $form['footermap_heading'] = array(
    '#type' => 'radios',
    '#title' => t('Enable Menu Heading'),
    '#description' => t('This will enable the menu-name property (e.g. navigation, user-menu) to be displayed as the heading above each menu column. This is nice if you have your menus setup in distinct blocks or controlled via the recurse-limit property above.'),
    '#options' => array(
      t('No'),
      t('Yes'),
    ),
    '#default_value' => variable_get('footermap_heading', 0),
  );
  $form['top_menu'] = array(
    '#type' => 'textfield',
    '#title' => t('Drupal Root Menu'),
    '#description' => t('Set the menu id to use as the top level.  Default is to start at 0 i    .e. primary menus - primary, secondary, and navigation menus'),
    '#default_value' => variable_get('top_menu', variable_get('menu_primary_menu', 0)),
    '#max_length' => 3,
    '#size' => 3,
  );
  $avail_menus = footermap_get_primary_menus(variable_get('top_menu', variable_get('menu_primary_menu', 0)));
  $form['avail_menus'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Available menus'),
    '#description' => t('Limit the available menus under the "top menu" setting above to the following menu items and their children.'),
    '#options' => $avail_menus,
    '#default_value' => variable_get('avail_menus', array()),
  );
  $form['sys_menus'] = array(
    '#type' => 'radios',
    '#title' => t('Enable System Menu Items'),
    '#description' => t('Enable system menus to be displayed. This is disabled by default, bu    t is known to not work with Views menus because of a bug in <a href="http://api.drupal.org/api/function/hook_menu_link_alter/7">hook_menu_link_alter</a>'),
    '#options' => array(
      t('No'),
      t('Yes'),
    ),
    '#default_value' => variable_get('sys_menus', 0),
  );
  $form['footer_legacy'] = array(
    '#type' => 'radios',
    '#title' => t('Enable Legacy Footermap'),
    '#description' => t('Enable footermap to be inserted in the $closure as well as in a block. You pr    obably do not want to enable this unless you\'re upgrading from an older version.'),
    '#options' => array(
      t('No'),
      t('Yes'),
    ),
    '#default_value' => variable_get('footer_legacy', 0),
  );
  $form['footer_cache'] = array(
    '#type' => 'radios',
    '#title' => t('Enable Footermap Cache'),
    '#description' => t('Enable caching of footermap. This is only recommended if you are using the legacy footeramp. This option will be removed in the future when the legacy footeramp is deprecated. Caching is done via the block system.'),
    '#options' => array(
      t('No'),
      t('Yes'),
    ),
    '#default_value' => variable_get('footer_cache', 0),
  );
  return system_settings_form($form);
}

/*
 * menu_link_alter hook -- clear the footer cache
 * @param item item unused
 * @param menu menu unused
 */
function footermap_menu_link_alter(&$item, $menu) {
  if (variable_get('footer_cache', 0) == 0) {
    return;
  }
  $footercache = cache_get('footermap', 'cache');
  $mlidregex = '/' . $item['mlid'] . '/';
  if (preg_match($mlidregex, $footercache->data) == 0) {
    return;
  }

  // okay let's clear cache
  cache_clear_all('footermap', 'cache');
}

/*
 * Implementation of hook_footer
 */
function footermap_footer() {
  if (variable_get('footer_legacy', 0) == 0) {
    return;
  }
  if (variable_get('footer_cache', 0) == 1) {
    $vartime = cache_get('variables', 'cache')->created;
    $cached = cache_get('footermap', 'cache');
  }
  if (isset($o->data) && $o->created >= $vartime) {
    return $cached;
  }
  drupal_add_css(drupal_get_path('module', 'footermap') . '/footermap.css', 'module', 'all', TRUE);
  $o = _footermap_render();
  if (variable_get('footer_cache', 0) == 1) {
    cache_set('footermap', $o, 'cache', CACHE_TEMPORARY);
  }
  return $o;
}

/*
 * Implementation of hook_block
 */
function footermap_block($op = 'list', $delta = 0, $edit = array()) {
  if ($op == 'list') {
    $blocks = array();
    $blocks[] = array(
      'info' => t('Footermap block'),
      'weight' => 5,
      'status' => 1,
      'region' => 'footer',
    );
    return $blocks;
  }
  else {
    if ($op == 'view') {
      drupal_add_css(drupal_get_path('module', 'footermap') . '/footermap.css', 'module', 'all', TRUE);
      $block = array(
        'subject' => t(''),
        'content' => _footermap_render(),
      );
      return $block;
    }
  }
}

/*
 * footermap_render
 * @return HTML output
 */
function _footermap_render() {
  $recurse = variable_get('recurse_limit', 0);
  $base = variable_get('top_menu', 0);
  $menus = menu_get_menus(TRUE);
  $mapref = array();
  $disp_heading = variable_get('footermap_heading', 0);
  $o = '';
  footermap_get_menu($base, $mapref, $recurse, 0);
  $n = 0;
  foreach ($mapref as $key => $block) {
    $heading = $disp_heading == 1 ? $menus[$key] : '';
    $o .= '<div class="footermap-col" id="footermap-col-' . $n . ' footermap-col-' . $key . '">';
    if ($heading != '') {
      $o .= '<h3>' . $heading . '</h3>';
    }
    $o .= theme('links', $block, array(
      'class' => 'footermap-item links',
    )) . '</div>';
    $n++;
  }
  $o = '<div class="footermap">' . $o . '</div>';
  return $o;
}

/*
 * footermap_get_menu
 * @param $mlid the menu link id to use as the plid
 * @param &$mapref reference to the footermap array of links
 * @param $recurse our recurse limit
 * @param $level the current level
 * @param $arrcnt 
 */
function footermap_get_menu($mlid, &$mapref, $recurse, $level) {
  global $user;
  if ($recurse != 0 && $level >= $recurse) {
    return;
  }
  $avail_menus = variable_get('avail_menus', array());
  if (variable_get('sys_menus', 0) == 0) {
    $system_menu = " AND ml.module <> 'system' ";
  }
  else {
    $system_menu = ' ';
  }
  $sql = 'SELECT * FROM {menu_links} ml LEFT OUTER JOIN {menu_router} mr ON (ml.router_path = mr.path) WHERE ml.plid = %d AND ml.hidden = 0' . $system_menu . 'ORDER BY ml.plid, ml.weight, ml.link_title, mr.title';
  $res = db_query($sql, $mlid);
  while ($h = db_fetch_object($res)) {

    // available menus check
    if (!_footermap_check_menu_item($avail_menus, $h)) {
      continue;
    }

    // access check for menu router items
    if (isset($h->path)) {
      if (empty($h->access_callback)) {

        // automatic fail
        continue;
      }
      $map = array();
      $args = array();
      $map = explode('/', $h->link_path);
      if (!empty($h->to_arg_functions)) {
        _menu_link_map_translate($map, $h->to_arg_functions);
      }
      $args = menu_unserialize($h->access_arguments, $map);

      // There's no way that I can do access callback stuff for every
      // custom access callback with custom arguments under the sun.
      // It's out of scope of this module, and a non-issue.
      if ($h->access_callback == 'user_access') {
        if (!empty($args[0])) {
          if (!user_access($args[0], $user)) {
            continue;
          }
        }
      }
      else {
        if ($h->access_callback == 'node_access') {
          if (!node_access($args[0], node_load($args[1]))) {
            continue;
          }
        }
      }
    }
    $mapref[$h->menu_name]['menu-' . $h->mlid] = array(
      'href' => $h->link_path,
      'title' => $h->link_title,
    );
    if ($h->has_children == 1) {
      footermap_get_menu($h->mlid, $mapref, $recurse, $level + 1);
    }
  }
}

/*
 * footermap_get_primary_menus
 * @param $top_menu
 * @return array
 */
function footermap_get_primary_menus($top_menu) {
  $ret = array();
  if (!is_numeric($top_menu)) {
    return $ret;
  }
  $res = db_query("SELECT * FROM {menu_links} WHERE plid = %d AND hidden = 0", $top_menu);
  while ($menu = db_fetch_object($res)) {
    $ret[$menu->menu_name] = t($menu->menu_name);
  }
  return $ret;
}

/*
 * _footermap_check_menu_item
 * @param $avail_menus array
 * @param $item 
 * @return bool
 */
function _footermap_check_menu_item($avail_menus, $item) {
  foreach ($avail_menus as $key => $mn) {
    if ($mn === $item->menu_name) {
      return TRUE;
    }
  }
  return FALSE;
}