You are here

function _tableofcontents_replace_headers in Table of Contents 7

Same name and namespace in other branches
  1. 6.3 tableofcontents.pages.inc \_tableofcontents_replace_headers()
  2. 7.2 tableofcontents.inc \_tableofcontents_replace_headers()

This function changes a header attributes. It adds an identifier in case there are none. It registers the identifier if there is already one.

Note that the attributes (2nd match) always starts with a space if it exists.

Parameters

$matches The matches (level, attributes and title):

Return value

The header with an identifier.

1 string reference to '_tableofcontents_replace_headers'
_tableofcontents_headers in ./tableofcontents.pages.inc
This function goes through all the headers found in the text.

File

./tableofcontents.pages.inc, line 40
Applies the filter functions.

Code

function _tableofcontents_replace_headers($matches) {
  global $_tableofcontents_toc;
  static $toc_translations, $duplicates_error;
  $h = new TOC_Header();
  $h->level = $matches[2];
  $h->attributes = $matches[3];
  $h->title = $matches[4];

  // increase this level and reset all the sub-levels
  ++$_tableofcontents_toc->counters[$h->level];
  for ($l = $h->level + 1; $l <= 6; ++$l) {
    $_tableofcontents_toc->counters[$l] = 0;
  }
  if ($_tableofcontents_toc->level_from > $h->level) {
    $_tableofcontents_toc->level_from = $h->level;
  }
  $_tableofcontents_toc->level_to = $h->level;

  // determine the min./max. on the spot
  if ($_tableofcontents_toc->header_min) {
    if ($_tableofcontents_toc->header_min > $h->level) {
      $_tableofcontents_toc->header_min = $h->level;
    }
  }
  else {
    $_tableofcontents_toc->header_min = $h->level;
  }
  if ($_tableofcontents_toc->header_max) {
    if ($_tableofcontents_toc->header_max < $h->level) {
      $_tableofcontents_toc->header_max = $h->level;
    }
  }
  else {
    $_tableofcontents_toc->header_max = $h->level;
  }

  // check for existing id and use that if found
  if (preg_match('/\\sid=(?:"([^"]*?)"|\'([^\']*?)\'|([^\\s"\'>]))/i', $h->attributes, $id)) {

    // id[1] is with "
    // id[2] is with '
    // id[3] is without any quotes
    $h->identifier = $id[1] ? $id[1] : ($id[2] ? $id[2] : $id[3]);

    // check for unicity
    foreach ($_tableofcontents_toc->headers as $header) {
      if ($header->identifier == $h->identifier) {
        if (!isset($duplicates_error)) {
          $duplicates_error = TRUE;
          drupal_set_message(t('Two or more anchor identifiers match each others. One of them will be modified. This error happens when some anchor identifiers are generated automatically and others are predefined. Or both are predefined and the operator made a mistake (maybe a copy &amp; paste?)'), 'warning');
        }

        // make it unique anyway
        $h->identifier .= $_tableofcontents_toc->id_separator . $_tableofcontents_toc->header_id++;
        $h->attributes = preg_replace('/\\sid=(?:"([^"]*?)"|\'([^\']*?)\'|([^\\s"\'>]))/i', ' id="' . $h->identifier . '"', $h->attributes);
        break;
      }
    }
  }
  else {
    switch ($_tableofcontents_toc->id_generator) {
      case 'random':

        // generate a random ID and then ensure unicity
        do {
          $id = user_password(8);

          // system function to generate a password from letters/digits
          $found = FALSE;
          foreach ($_tableofcontents_toc->headers as $header) {
            if ($header->identifier == $id) {
              $found = TRUE;
              break;
            }
          }
        } while ($found);
        break;
      case 'increment':
        $id = $_tableofcontents_toc->identifier_introducer . $_tableofcontents_toc->id_separator . $_tableofcontents_toc->header_id++;
        break;
      case 'sections':
        $id = $_tableofcontents_toc->identifier_introducer;
        for ($idx = $_tableofcontents_toc->level_from; $idx <= $_tableofcontents_toc->level_to; ++$idx) {
          $id .= $_tableofcontents_toc->id_separator . $_tableofcontents_toc->counters[$idx];
        }
        break;
      case 'custom':

        // the callee has to edit the $h->identifier and $h->attributes fields
        module_invoke_all('anchor_identifier', $_tableofcontents_toc, $h);
        $id = '';
        break;
      default:

        // case 'title'

        /* no existing identifier, create one using the header title
         *
         * HTML 4.01
         *
         * http://www.w3.org/TR/html4/types.html#h-6.2
         *
         * ID and NAME tokens must begin with a letter ([A-Za-z]) and
         * may be followed by any number of letters, digits ([0-9]),
         * hyphens ("-"), underscores ("_"), colons (":"), and periods (".").
         *
         * 1. convert &nbsp; and other spaces into underscores
         * 2. convert &mdash; or &ndash; to '--'
         * 3. convert &amp; to 'and'
         * 4. remove any other entities
         * 5. remove any incompatible character
         * 6. remove digits at the start of the name (we could also add a letter?)
         *
         * sanitize accents by luron & deviantintegral (e.g. �=>e)
         * thanks to pathauto module for i18n-ascii.txt file */
        if (!isset($toc_translations)) {
          $path = drupal_get_path('module', 'tableofcontents');
          $toc_translations = parse_ini_file($path . '/i18n-ascii.txt');
        }
        $title = strtr(strip_tags($h->title, $_tableofcontents_toc->allowed_tags), $toc_translations);
        $allowed_chars = '';
        if (empty($_tableofcontents_toc->id_stripping['dashes'])) {
          $allowed_chars = '-';
        }
        $allowed_chars .= 'A-Za-z';
        if (empty($_tableofcontents_toc->id_stripping['digits'])) {
          $allowed_chars .= '0-9';
        }
        if (empty($_tableofcontents_toc->id_stripping['periods'])) {
          $allowed_chars .= '.';
        }
        if (empty($_tableofcontents_toc->id_stripping['underscores'])) {
          $allowed_chars .= '_';
        }
        if (empty($_tableofcontents_toc->id_stripping['colons'])) {
          $allowed_chars .= ':';
        }
        $id = preg_replace(array(
          '/&nbsp;|\\s/',
          '/\'/',
          '/&mdash;/',
          '/&ndash;/',
          '/&amp;/',
          '/&[a-z]+;/',
          '/[^' . $allowed_chars . ']/',
          '/^[-0-9._:]+/',
          '/__+/',
        ), array(
          '_',
          // &nbsp; and spaces
          '-',
          // apostrophe, so it makes things slightly more readable
          '--',
          // &mdash;
          '--',
          // &ndash;
          'and',
          // &amp;
          '',
          // any other entity
          '',
          // any character that is invalid as an ID name
          '',
          // any digits at the start of the name
          '_',
        ), strip_tags($title));
        if (!$id) {

          // no identifier (i.e. title is composed exclusively of digits, entities, etc.)
          $id = $_tableofcontents_toc->identifier_introducer . $_tableofcontents_toc->id_separator . $_tableofcontents_toc->header_id++;
        }
        break;
    }
    if ($id != '') {

      // ensure unicity
      foreach ($_tableofcontents_toc->headers as $header) {
        if ($header->identifier == $id) {
          $id .= $_tableofcontents_toc->id_separator . $_tableofcontents_toc->header_id++;
          break;
        }
      }
      $h->identifier = $id;

      // create a new header including the generated identifier
      $h->attributes .= ' id="' . $id . '"';
    }
  }
  $h->number = theme('tableofcontents_number', array(
    'toc' => $_tableofcontents_toc,
  ));
  if ($_tableofcontents_toc->number_headers) {
    $number = $h->number;
  }
  else {
    $number = '';
  }
  $result = $matches[1] . '<h' . $h->level . $h->attributes . '>' . $number . $h->title . '</h' . $h->level . '>';

  // save that header
  $_tableofcontents_toc->headers[] = $h;

  // Add a back to top before the header?
  if ($_tableofcontents_toc->back_to_top && $h->level >= $_tableofcontents_toc->back_to_top_minlevel && $h->level <= $_tableofcontents_toc->back_to_top_maxlevel) {
    switch ($_tableofcontents_toc->back_to_top_location) {
      case 'header':
        $result .= $_tableofcontents_toc->back_to_top_link;
        break;
      default:

        //case 'bottom':
        if (!$_tableofcontents_toc->first_header) {
          $result = $_tableofcontents_toc->back_to_top_link . $result;
        }
        break;
    }
  }
  $_tableofcontents_toc->first_header = FALSE;
  return $result;
}