<?php

/**
 * Lexicon is used to create lists of terms and definitions to use on a website and optionally
 * mark them in the content with an indicator.
 *
 * The Lexicon module lists all lexicon terms on a lexicon page. Optionally it scans posts for
 * lexicon terms (and their synonyms) in the body. If found, the lexicon indicator is inserted
 * after the term, or the term is turned into an indicator link depending on the settings. By
 * hovering over the indicator, users may learn the definition of that term. Clicking leads the
 * user to that term presented within the whole lexicon.
 * @file
 */

function lexicon_help($path, $args = array()) {
  $output = '';

  switch ($path) {
    case 'admin/help#lexicon':
      return t('<p>Lexicon is used to create lists of terms and definitions to use on a website and optionally mark them in the content with an indicator</p>
      <p>The Lexicon module lists all lexicon terms on a lexicon page. Optionally it scans posts for lexicon terms (and their synonyms) in the body. If found, the lexicon indicator is inserted after the term, or the term is turned into an indicator link depending on the settings. By hovering over the indicator, users may learn the definition of that term. Clicking leads the user to that term presented within the whole lexicon.</p>
      <p>Lexicon terms are managed as vocabularies within the taxonomy module. To get started with lexicon, create a new vocabulary on the !taxonomy_admin page. The vocabulary need not be associated with any modules, although you can add detailed description to terms by attaching (story or other type of) nodes to them. Add a few terms to the vocabulary. The term title should be the lexicon entry and the description should be its definition. You can make use of the synonym, and related terms features. These features impact the display of the lexicon when viewed in an overview.</p>
      <p>Next, you have to set up the Lexicon module and enable the Lexicon filter on input formats. On the !input_formats page, select an input format to configure. Select the Lexicon filter checkbox and press "Save configuration".</p>
      <p>Administration of lexicon requires %permissions permissions.</p>',
      array('%permissions' => join(', ',
        array(t('administer taxonomy'), t('access administration pages'), t('administer lexicon'))),
        '!taxonomy_admin' => l(t('administer >> content >> taxonomy'), 'admin/content/taxonomy'),
        '!input_formats' => l(t('administer >> site configuration >> input formats'), 'admin/settings/filters'),
        '!lexicon' => l(t('administer >> site configuration >> lexicon'), 'admin/settings/lexicon'),
        '!lexicons' => l(t('lexicons'), 'lexicon')));
      break;

    case 'admin/settings/lexicon':
      return '<p><big>'. t('This page and its tabs allow you to control how the Lexicon module functions.') .'</big></p>';
      break;

    case 'admin/modules#description':
      return t('Maintain one or more lexicons on your site.');
      break;

    case 'lexicon_search#noresults':
      return t('<ul>
<li>Check if your spelling is correct.</li>
<li>Remove quotes around phrases to match each word individually: <em>"blue smurf"</em> will match less than <em>blue smurf</em>.</li>
<li>Consider loosening your query with <em>OR</em>: <em>blue smurf</em> will match less than <em>blue OR smurf</em>.</li>
</ul>');
  }
}

/**
 * Implementation of hook_block().
 */
function lexicon_block($op = 'list', $delta = 0, $edit = array()) {
  $blocks = array();
  switch ($op) {
    case 'list':
      $blocks[0]['info'] = t('Lexicon: Search');
      $blocks[1]['info'] = t('Lexicon: Random');
      return $blocks;

    case 'view':
      switch ($delta) {
        case 0:
          $blocks['subject'] = t('Search Lexicon');
          $blocks['content'] = drupal_get_form('lexicon_search_form');
          return $blocks;

        case 1:
          $interval = variable_get("lexicon_block_{$delta}_interval", 0) * variable_get("lexicon_block_{$delta}_step", 0);
          $last = variable_get("lexicon_block_{$delta}_last", 0);
          if ($last + $interval < time()) {
            // Time to get a new selection.
            $saved_vids = variable_get("lexicon_block_{$delta}_vids", NULL);
            if (is_null($saved_vids)) {
              $blocks['content'] = t('Lexicon block !blk has not been configured.', array('!blk' => $delta));
              return $blocks;
            }
            $vids = array_filter($saved_vids);
            if (count($vids) == 0) {
              $vids = variable_get('lexicon_vids', array());
            }
            $placeholders = implode(',', array_fill(0, count($vids), '%d'));
            $result = db_fetch_object(db_query_range('SELECT tid, vid FROM {term_data} WHERE vid in ('. $placeholders .') ORDER BY RAND()', $vids, 0, 1));
            $tid = $result->tid;
            $vid = $result->vid;
            // Set now as the last selection and save that tid and vid.
            variable_set("lexicon_block_{$delta}_last", time());
            variable_set("lexicon_block_{$delta}_tid", $tid);
            variable_set("lexicon_block_{$delta}_vid", $vid);
          }
          else {
            // Get the current selected tid.
            $tid = variable_get("lexicon_block_{$delta}_tid", 0);
            $vid = variable_get("lexicon_block_{$delta}_vid", 0);
          }

          $term = taxonomy_get_term($tid);
          $blocks['content'] = theme('lexicon_block_term', $vid, $term, variable_get("lexicon_block_{$delta}_link", TRUE));
          return $blocks;
      }

    case 'configure':
      $form = array();
      switch ($delta) {
        case 0:
          // Search block - no config.
          return $form;

        case 1:
          $vids = array();
          $vid_list = variable_get('lexicon_vids', array());
          foreach ($vid_list as $vid) {
            if ($vid != 0) {
              $voc = taxonomy_vocabulary_load($vid);
              $vids[$vid] = check_plain($voc->name);
            }
          }
          $form['vids'] = array(
            '#type' => 'checkboxes',
            '#title' => t('Choose from'),
            '#description' => t('Select the vocabularies to choose a term from.'),
            '#options' => $vids,
            '#default_value' => variable_get("lexicon_block_{$delta}_vids", array()),
            '#prefix' => '<div class="lexicon_checkboxes">',
            '#suffix' => '</div>',
            );

          $form['interval'] = array(
            '#type' => 'textfield',
            '#size' => 4,
            '#maxlength' => 3,
            '#default_value' => variable_get("lexicon_block_{$delta}_interval", 0),
            '#field_prefix' => '<strong>'. t('Update every') .'</strong>&nbsp;',
            '#prefix' => '<div class="container-inline lexicon-interval">',
            );

          $form['step'] = array(
            '#type' => 'select',
            '#default_value' => variable_get("lexicon_block_{$delta}_step", 0),
            '#options' => array(
                1 => t('seconds'),
                60 => t('minutes'),
                3600 => t('hours'),
                86400 => t('days'),
                ),
            '#suffix' => '</div>',
            '#description' => t('How often do you want a new term? Leaving this blank or zero means every time.'),
            );

          $form['link'] = array(
            '#type' => 'checkbox',
            '#title' => t('Show term as link'),
            '#default_value' => variable_get("lexicon_block_{$delta}_link", TRUE),
            '#description' => t('If selected, this option causes the term name to be made a link to the lexicon entry.'),
            );

          return $form;
      }
      return $form;

    case 'save':
      switch ($delta) {
        case 0:
          // Search block - no config.
          break;

        case 1:
          variable_set("lexicon_block_{$delta}_vids", $edit['vids']);
          if (!$edit['interval'] || !is_numeric($edit['interval'])) {
            // Make interval numeric;
            $edit['interval'] = 0;
          }
          variable_set("lexicon_block_{$delta}_interval", $edit['interval']);
          variable_set("lexicon_block_{$delta}_step", $edit['step']);
          variable_set("lexicon_block_{$delta}_link", $edit['link']);
          break;
      }
  }
}

/**
 * Implementation of hook_menu().
 */
function lexicon_menu() {
  $path = variable_get('lexicon_path', 'lexicon');
  $items[$path . '/search'] = array(
    'title' => 'Lexicon Search',
    'page callback' => 'lexicon_search_results',
    'page arguments' => array(2),
    'access arguments' => array('access lexicon'),
    'type' => MENU_CALLBACK,
    );

  $vids = array();
  $vids = variable_get('lexicon_vids', array());

  foreach ($vids as $vid) {
    //Don't create menu items for vocabularies that are not setup as being Lexicon vocabularies
    if ($vid != 0) {
      $lexicon_path = variable_get('lexicon_path_'. $vid, 'lexicon/' . $vid);
      $lexicon_title = variable_get('lexicon_title_' . $vid, t('Lexicon'));
      $items[$lexicon_path] = array(
        'title' => $lexicon_title,
        'page callback' => 'lexicon_page',
        'access arguments' => array('access lexicon'),
        'type' => MENU_SUGGESTED_ITEM,
      );
    }
  }

  $items['admin/settings/lexicon'] = array(
    'title' => 'Lexicon Settings',
    'page callback' => 'lexicon_settings_page',
    'description' => 'Select how you want the Lexicon module to behave.',
    'access arguments' => array('administer lexicon'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'lexicon.admin.inc',
    );

  $items['admin/settings/lexicon/general'] = array(
    'title' => 'General',
    'description' => 'General settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('lexicon_general_settings_form'),
    'access arguments' => array('administer lexicon'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -3,
    'file' => 'lexicon.admin.inc',
    );

  $items['admin/settings/lexicon/pathsandtitles'] = array(
    'title' => 'Paths and titles',
    'description' => 'Paths and titles settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('lexicon_paths_and_titles_form'),
    'access arguments' => array('administer lexicon'),
    'type' => MENU_LOCAL_TASK,
    'weight' => -3,
    'file' => 'lexicon.admin.inc',
    );

  $items['admin/settings/lexicon/alphabet'] = array(
    'title' => 'Alphabet',
    'access arguments' => array('administer lexicon'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('lexicon_alphabet_form'),
    'description' => 'Alphabet settings.',
    'type' => MENU_LOCAL_TASK,
    'weight' => 0,
    'file' => 'lexicon.admin.inc',
    );

  $items[$path . '/term/%'] = array(
    'title' => 'Lexicon',
    'page callback' => 'lexicon_term',
    'page arguments' => array(2),
    'access arguments' => array('access lexicon'),
    'type' => MENU_CALLBACK,
    );

    return $items;
}

/**
 * Implementation of hook_perm().
 */
function lexicon_perm() {
  return array('administer lexicon', 'access lexicon');
}

/**
 * Implementation of hook_init().
 */
function lexicon_init() {
  $path = drupal_get_path('module', 'lexicon');
  drupal_add_css($path . '/css/lexicon.css');
}

function lexicon_search_form($form_state, $keys = NULL) {
  $form['#attributes'] = array('class' => 'lexicon search-form');
  $form['keys'] = array(
    '#type' => 'textfield',
    '#title' => '',
    '#default_value' => $keys,
    '#size' => 15,
    );
  $form['search'] = array(
    '#type' => 'submit',
    '#value' => t('Search'),
    );
  return $form;
}

function lexicon_search_form_submit($form, &$form_state) {
  $keys = trim($form_state['values']['keys']);
  $form_state['redirect'] = 'lexicon/search/'. $keys;
}

function lexicon_search_results($keys = NULL) {
  $vids = variable_get('lexicon_vids', array());
  $output = '<div class="lexicon-list">';

  $sql = db_rewrite_sql("SELECT t.tid, t.vid FROM {term_data} t WHERE t.vid IN (". db_placeholders($vids) .") AND (t.description LIKE '%%%s%%' OR t.name LIKE '%%%s%%')", 't}', 'tid');
  $vars = $vids;
  // Yes, we need this twice.
  $vars[] = $keys;
  $vars[] = $keys;
  $result = db_query($sql, $vars);
  $found = NULL;
  while ($row = db_fetch_object($result)) {
    ++$count;
    if ($count == 1) {
      $found .= '<dl>';
    }
    $term = taxonomy_get_term($row->tid);
    $found .= theme('lexicon_overview_item', $row->vid, $term, TRUE);
  }
  if ($found) {
    $found .= '</dl>';
  }
  else {
    $found = drupal_get_form('lexicon_search_form', $keys) .'<p>'. t('Your search yielded no results') . lexicon_help('lexicon_search#noresults') .'</p>';
  }

  $output .= theme('box', t('Lexicon search results'), $found);
  return $output ."</div>\n";
}

function theme_lexicon_search_form($form) {
  return '<div class="container-inline">'. drupal_render($form) .'</div>';
}

/**
 * Implementation of hook_theme().
 */
function lexicon_theme() {
  return array(
    'lexicon_search_form' => array(
      'arguments' => array('form'),
      ),
    'lexicon_overview_item' => array(
      'arguments' => array('vid', 'term', 'show_desc', 'destination'),
      ),
    'lexicon_block_term' => array(
      'arguments' => array('vid', 'term', 'link'),
      ),
    );
}

function lexicon_term($tid) {
  $result = db_query(db_rewrite_sql('SELECT * FROM {term_data} t WHERE t.tid=%d', 't', 'tid'), $tid);
  $output .= '<div class="lexicon-list">'."\n";
  while ($row = db_fetch_object($result)) {
    $term = taxonomy_get_term($row->tid);
    drupal_set_title(check_plain($term->name));
    $vid = $row->vid;
    $output .= '<dl>'. theme('lexicon_overview_item', $vid, $term, TRUE) .'</dl>';
  }
  $output .= "</div>\n";

  $tree = taxonomy_get_tree($vid);
  $alphabar = _lexicon_alphabar($vid, $tree);
  $output = $alphabar .'<br/>'. $output;
  return $output;
}

function lexicon_filter_tips($delta, $format, $long = FALSE) {
  $block_tags = explode(' ', variable_get("lexicon_blocking_tags", 'abbr acronym'));
  $standard_blocks = array('a');
  foreach ($standard_blocks as $tag) {
    if (!in_array($tag, $block_tags)) {
      $block_tags[] = check_plain($tag);
    }
  }

  foreach ($block_tags as $key => $tag) {
    if ($tag[0] == '.') {
      $block_tags[$key] = 'span class="'. check_plain(drupal_substr($tag, 1)) .'"';
    }
  }

  sort($block_tags, SORT_STRING);
  $blocked = implode(', ', $block_tags);
  $more = ' '. t('Additionally, these HTML elements will not be scanned: %blocked.', array('%blocked' => $blocked));

  if ($long) {
    return t('The Lexicon module will automatically mark terms that have been defined in the lexicon vocabulary with links to their descriptions. These marks depend on the settings and may be a superscript character or an icon, or the term may be turned into an acronym, cite, or abbreviation. If there are certain phrases or sections of text that should be excluded from lexicon marking and linking, use the special markup, [no-lexicon] ... [/no-lexicon].') . $more;
  }
  else {
    return t('Lexicon terms will be automatically marked with links to their descriptions. If there are certain phrases or sections of text that should be excluded from lexicon marking and linking, use the special markup, [no-lexicon] ... [/no-lexicon].') . $more;
  }
}

/**
 * Implementation of hook_filter().
 */
function lexicon_filter($op, $delta = 0, $format = -1, $text = "") {
  switch ($op) {
    case 'list':
      return array(0 => t('Lexicon filter'));

    case 'description':
      return t('Maintain one or more lexicons on your site.');

    case 'process':
      return _lexicon_filter_process($text);

    case 'no cache':
      return FALSE;

    default:
      return $text;
  }
}

/**
 * Implementation of hook_taxonomy().
 */
function lexicon_taxonomy($op, $type, $array = NULL) {
  // Anytime a vocabulaty/term is changed the filter cache must be cleared to reflect the changes in the marking of terms in the content
  if ($type == 'term' && $array) {
    _lexicon_clear_filter_cache($array['vid'], FALSE);
  }
}

/**
 * Implementation of hook_user().
 */
function lexicon_user($type, $edit, $user) {
  switch ($type) {
    case 'form':
      if (variable_get('lexicon_disable_indicator', FALSE)) {
        $form['content_lexicon'] = array(
          '#type' => 'fieldset',
          '#title' => t('Lexicon Indicators'),
        );
        $form['content_lexicon']['lexicon_disable_indicator'] = array(
          '#type' => 'checkbox',
          '#title' => t('Disable Lexicon indicators'),
          '#return_value' => 1,
          '#default_value' => $user->lexicon_disable_indicator,
          '#description' => t('Check this box to disable the display of Lexicon indicators.'),
        );
        return $form;
      }
      break;
  }
}

/**
 * Mark lexicon vocabulary terms in the body text.
 */
function _lexicon_filter_process($text) {
  global $user;
    if ($user->lexicon_disable_indicator && variable_get('lexicon_disable_indicator', FALSE)) {
    return $text;
  }

  if (strcmp($_REQUEST['q'], 'fckeditor/xss') == 0) {
    return $text;
  }

  $current_term = 0;
  if (strcmp(arg(0), 'taxonomy') == 0 && strcmp(arg(1), 'term') == 0 && arg(2) > 0) {
    $current_term = arg(2);
  }

  if (variable_get("lexicon_mark_terms", 0) == 1 ) {
    $text = ' '. $text .' ';
    $replace_mode = variable_get("lexicon_replace", 'superscript');
    $link_style = variable_get("lexicon_link", 'normal');
    $absolute_link = ($link_style == 'absolute');
    $terms = _lexicon_get_terms();
    $vids = variable_get('lexicon_vids', array());
    $terms_replace = array();
    $tip_list = array();
    if (is_array($terms)) {
      foreach ($terms as $term) {
        if ($current_term == $term->tid) {
          continue;
        }
        $term_title = $term->description;
        $fragment = NULL;
        if ($term->nodes > 0) {
          $linkto = taxonomy_term_path($term);
        }
        elseif (!empty($vids) && (variable_get('lexicon_click_option', 0) == 1)) {
          if (variable_get('lexicon_hide_if_empty', 1) && empty($term_title)) {
            continue;
          }
          if (variable_get('lexicon_page_per_letter', FALSE)) {
            $linkto = variable_get('lexicon_path_' . $term->vid, 'lexicon/' . $term->vid) .'/letter_'. drupal_strtolower(drupal_substr($term->name, 0, 1));
          }
          else {
            $linkto = variable_get('lexicon_path_' . $term->vid, 'lexicon/' . $term->vid);
          }

          //Make sure the id of the anchor is a valid one and doe not contain illegal characters or format
          $fragment = _lexicon_create_valid_id($term->name);
        }
        else {
          $linkto = 'taxonomy/term/'. $term->tid;
        }
        $ins_before = $ins_after = NULL;
        $term_class = variable_get('lexicon_term_class', 'lexicon-term');

        switch ($replace_mode) {
          case 'superscript':
            $ins_after = '<sup class="lexicon-indicator" title="'. $term_title .'">';
            if ($link_style == 'none') {
              $ins_after .= variable_get("lexicon_superscript", 'i');
            }
            else {
              $ins_after .= l(variable_get("lexicon_superscript", 'i'), $linkto,
                array('attributes' => array('title' => $term_title, 'class' => 'lexicon-indicator'),
                'fragment' => $fragment, 'absolute' => $absolute_link));
            }
            $ins_after .= '</sup>';
            break;

          case 'abbr':
            if ($link_style == 'none') {
              $ins_before .= '<span class="'. $term_class .'" title="'. $term_title .'"><'. $replace_mode .' title="'. $term_title .'">';
              $ins_after .= '</'. $replace_mode .'></span>';
            }
            else {
              $ins_before .= '<'. $replace_mode .' title="'. $term_title .'"><a class="'. $term_class .'" href="'. url($linkto, array('fragment' => $fragment, 'absolute' => $absolute_link)) .'" title="'. $term_title .'">';
              $ins_after .= '</a></'. $replace_mode .'>';
            }
            break;

          case 'acronym link':
            $replace_mode = 'acronym';

          case 'acronym':
          case 'cite':
          case 'dfn':
            if ($link_style == 'none') {
              $ins_after .= '</'. $replace_mode .'>';
            }
            else {
              $ins_before .= '<a class="lexicon-term" href="'. url($linkto, array('fragment' => $fragment, 'absolute' => $absolute_link)) .'">';
              $ins_after .= '</'. $replace_mode .'></a>';
            }
            $ins_before .= '<'. $replace_mode .' title="'. $term_title .'">';
            break;

          case 'clicktip':
          case 'hovertip':
            if ($link_style == 'none') {
              $ins_after .= '</span>';
            }
            else {
              $ins_before = '<a class="' . $term_class . '" href="' . url($linkto, array('fragment' => $fragment, 'absolute' => $absolute_link)) .'">';
              $ins_after .= '</span></a>';
            }
            $ins_before .= "<span $replace_mode=$term->tid>";
            if (!isset($tip_list[$term->tid])) {
              $tip_list[$term->tid] = decode_entities('<span class="'. $replace_mode .'" id="'. $term->tid .'">'. $term->image . $term_title .'</span>');
            }
            $ins_after .= $tip_list[$term->tid];
            break;

          case 'iconterm':
            // Icon format, plus term link.
            $img = '<img src="'. base_path() . variable_get("lexicon_icon", '/imgs/lexicon.gif') ."\" />";
            if ($link_style == 'none') {
              $ins_after .= $img;
            }
            else {
              $ins_before .= '<a class="lexicon-term" href="'. url($linkto, array('fragment' => $fragment, 'absolute' => $absolute_link)) .'">';
              $ins_after = $img . '</a>';
            }
            break;

          default:
            // Icon format.
            $img = '<img src="'. base_path() . variable_get("lexicon_icon", '/imgs/lexicon.gif') ."\" />";
            if ($link_style == 'none') {
              $ins_after .= $img;
            }
            else {
              $ins_after = l($img, $linkto, array('attributes' => array('title' => $term_title, 'class' => 'lexicon-icon'), 'fragment' => $fragment, 'absolute' => $absolute_link, 'html' => TRUE));
            }
            break;
        }

        // Replace term and synonyms with the desired new HTML code.
        $terms_replace[] = array(
          'synonyms' => $term->synonyms,
          'ins_before' => $ins_before,
          'ins_after' => $ins_after,
          );
        }
      }
      return _lexicon_insertlink($text, $terms_replace);
    }
  return $text;
}

/**
 * Insert lexicon links to $text after every matching $terms[i]['synonyms'] that is not inside a blocking tag.
 * $terms[i]['ins_before'] is prepended to the matches, $terms[i]['ins_after'] is appended to them.
 * Match type and replace mode all depend on user settings.
 * The text is scanned once for all blocking tags and matches, then those 'events' are sorted and handled one by one.
 */
function _lexicon_insertlink(&$text, &$terms) {
  $multibyte_enabled = extension_loaded('mbstring');
  if ($multibyte_enabled) {
    $mb_prefix = 'mb_';
  }
  else {
    $mb_prefix = NULL;
  }
  $case_sensitive = variable_get("lexicon_case", '1');
  $findfunc = $mb_prefix .'strpos';
  $findtagfunc = $mb_prefix .'strpos';

  $replaceall = variable_get("lexicon_replace_all", 0);

  $events = array();

  // Find blocking tags.
  $open_tags = array('[no-lexicon]', '<', '<a ', '[code');
  $close_tags = array('[/no-lexicon]', '>', '</a>', '[/code]');

  $user_tags = explode(' ', variable_get("lexicon_blocking_tags", 'abbr acronym'));
  foreach ($user_tags as $tag) {
    if (!empty($tag)) {
      if (ctype_alnum($tag)) {
        $open_tags[] = "<$tag";
        $close_tags[] = "</$tag>";
      }
      elseif ($tag[0] == '.') {
        $open_tags[] = '<span class="'. drupal_substr($tag, 1);
        $close_tags[] = "</span>";
      }
    }
  }

  $searchtext = $case_sensitive ? $text : drupal_strtolower($text);

  foreach ($open_tags as $i => $tag) {
    $offset=0;
    while (($offset = $findtagfunc($searchtext, $tag, $offset)) !== FALSE) {
      // Longer tags will override shorter '<' on the same offset.
      $events[$offset] = array('type' => 'open', 'which' => $i);
      $offset += drupal_strlen($tag);
    }
  }

  // Find match candidates.
  foreach ($terms as $i => $term) {
    foreach ($term['synonyms'] as $synonym) {
      if (!$case_sensitive) {
        $synonym = drupal_strtolower($synonym);
      }
      $offset=0;
      $first_match_found = FALSE;
      while (($offset = $findfunc($searchtext, $synonym, $offset)) !== FALSE) {
        $len = drupal_strlen($synonym);
        $match = drupal_substr($text, $offset, $len);
        // Only longer matches override shorter ones.
        if (!isset($events[$offset]) || drupal_strlen($events[$offset]['match']) < drupal_strlen($match)) {
          // Get synonym with case as in text.
          $events[$offset] = array('type' => 'match', 'which' => $i, 'match' => $match);
          if (!$replaceall) {
            $first_match_found = TRUE;
            break;
          }
        }
        $offset += $len;
      }
      if ($first_match_found && !$replaceall) {
        break;
      }
    }
  }

  ksort($events);

  $newtext = '';
  $parsed = 0; // Text was parsed from chars 0 to $parsed (exclusive).

  foreach ($events as $place => $event) {
    // Skip events inside blocking tag (they're already copied as is).
    if ($place < $parsed) {
      continue;
    }
    // Copy plain text (with no events).
      $newtext .= drupal_substr($text, $parsed, ($place - $parsed));
      $parsed = $place;
    // If a blocking tag is opened, skip to closing tag.
    if ($event['type'] == 'open') {
      $skip = $findtagfunc($text, $close_tags[$event['which']], $place);
      if ($skip === FALSE) {
        $skip = drupal_strlen($text);
      }
      // If the tag is [no-lexicon] - remove it with the closing tag (by incrementing $parsed without copying).
      if ($event['which'] == 0) {
        $parsed += drupal_strlen($open_tags[$event['which']]);
        $newtext .= drupal_substr($text, $parsed, ($skip - $parsed));
        $parsed = $skip + drupal_strlen($close_tags[$event['which']]);
      }
      // Copy text without changing it.
      else {
        $newtext .= drupal_substr($text, $parsed, ($skip - $parsed));
        $parsed = $skip;
      }
    }
    if ($event['type'] == 'match') {
      $matchlen = drupal_strlen($event['match']);
      $proper_match = FALSE;
      switch (variable_get("lexicon_match", 'b')) {
          case 'lr': // Require word break left or right.
            $proper_match = (_lexicon_is_boundary(drupal_substr($text, $place - 1, 1)) || _lexicon_is_boundary(drupal_substr($text, $place + $matchlen, 1 )));
            break;
          case 'b': // Require word break left and right.
            $proper_match = (_lexicon_is_boundary(drupal_substr($text, $place - 1, 1)) && _lexicon_is_boundary(drupal_substr($text, $place + $matchlen, 1)));
            break;
          case 'l':  // Require word break left.
            $proper_match = _lexicon_is_boundary(drupal_substr($text, $place - 1, 1));
            break;
          case 'r': // Require word break right.
            $proper_match = _lexicon_is_boundary(drupal_substr($text, $place + $matchlen, 1));
            break;
          case 's': // Match any substring.
          default:
            $proper_match = TRUE;
            break;
      }
      if ($proper_match) {
        $newtext .= $terms[$event['which']]['ins_before'] . $event['match'] . $terms[$event['which']]['ins_after'];
        $parsed += $matchlen;
      }
    }
  }

  // Append remaining part.
  return $newtext . drupal_substr($text, $parsed);
}

function lexicon_page($letter = NULL) {
  $found_vid = NULL;
  $curr_path = ($_GET['q']);

  if ($letter != NULL) {
    $letter = drupal_substr($letter, 7, 1);
  }

  $vids = variable_get('lexicon_vids', array());

  foreach ($vids as $vid) {
    $tmp_path = variable_get('lexicon_path_' . $vid, 'lexicon/' . $vid);
    if (strpos($curr_path, $tmp_path) !== FALSE) {
      $found_vid = $vid;
    }
  }

  if ($found_vid == NULL) {
    return _lexicon_list();
  }
  else {
    $voc = taxonomy_vocabulary_load($found_vid);
    //Set the active menu to be "primary-links" to make the breadcrumb work.
    //By default the active menu would be "navigation", causing only "Home" > node.title to be shown.
    menu_set_active_menu_name("primary-links");

    return lexicon_overview($voc, $letter);
  }
}

function _lexicon_alphabar($vid, &$tree) {
  $path = variable_get('lexicon_path_' . $vid, 'lexicon/' . $vid);
  $page_per_letter = variable_get('lexicon_page_per_letter', FALSE);

  if (variable_get('lexicon_suppress_unused', FALSE)) {
    // Just make it empty; it will be filled in below.
    $letters = array();
  }
  else {
    $lets = array_merge(variable_get('lexicon_alphabet', range('a', 'z')), variable_get('lexicon_digits', range('0', '9')));
    $letters = drupal_map_assoc($lets);
  }

  foreach ($tree as $key => $term) {
    $term->let = drupal_strtolower(drupal_substr($term->name, 0, 1));
    if ($page_per_letter) {
      $letters[$term->let] = l($term->let, $path .'/letter_'. $term->let, array('attributes' => array('class' => 'lexicon-item')));
    }
    else {
      $letters[$term->let] = l($term->let, $path, array('fragment' => 'letter_'. $term->let, 'attributes' => array('class' => 'lexicon-item')));
    }
  }
  $sep = ' '. variable_get('lexicon_alphabar_separator', '|') .' ';
  $output = '<div class="lexicon-links">'. implode($sep, $letters);

  $output .= '<div class="lexicon-alphabar-instructions">';
  $output .= t(variable_get('lexicon_alphabar_instruction', _alphabar_instruction_default()));
  $output .= "</div></div>\n";

  return $output;
}

function lexicon_overview($vocab, $letter = NULL) {
  $dest = drupal_get_destination();

  $module_path = drupal_get_path('module', 'lexicon');
  drupal_add_js($module_path . '/js/jquery.scrollTo-min.js');
  drupal_add_js($module_path . '/js/jquery.localscroll-min.js');
  if (variable_get('lexicon_local_links_scroll', 0) == 1) {
    drupal_add_js($module_path . '/js/lexicon.js');
  }

  $output = '<div id="'. drupal_strtolower(_lexicon_create_valid_id($vocab->name)) .'">';
  if ($vocab->description) {
    $output .= '<p class="lexicon-description">'. filter_xss_admin($vocab->description) .'</p>';
  }
  $vid = $vocab->vid;
  $path = variable_get('lexicon_path_' . $vid, 'lexicon/' . $vid);
  if ($letter) {
    drupal_set_title(t('!title beginning with !let', array('!title' => t(check_plain(variable_get('lexicon_title_' . $vid, 'lexicon/' . $vid))), '!let' => drupal_strtoupper($letter))));
  }
  $current_let = '';

  $no_overview = (variable_get('lexicon_click_option', 0) == 0) && (variable_get('lexicon_page_per_letter', FALSE) == TRUE);

  $show_desc = variable_get('lexicon_show_description', FALSE);
  $separate = variable_get('lexicon_separate_letters', FALSE);
  $tree = taxonomy_get_tree($vid);
  $synonyms = _lexicon_get_synonyms($vid);
  $output .= _lexicon_alphabar($vid, $tree);
  $output .= '<div class="lexicon-list">'."\n";
  if ($tree) {
    $notfirst = FALSE;
    foreach ($tree as $term) {
      $term->synonyms = $synonyms[$term->tid];
      // If we're looking for a single letter, see if this is it.
      if ((!$no_overview && !$letter) || $term->let == $letter) {
        // See if it's a new section.
        if ($term->let != $current_let) {
          //If this is not the first new section and we are not viewing a single letter then close the previous <dl>
          if ($notfirst == TRUE && $letter == NULL) {
            $output .= '</dl>';
            if ($separate && variable_get('lexicon_go_to_top_link', FALSE) == TRUE) {
              $output .= '<p><a href="#' . variable_get('lexicon_go_to_top_link_fragment', 'top') . '" title="' . t('Go to top') . '" class="lexicon_to_top_link">' . t('Go to top') . '</a></p>';
            }
          }
          $output .= "\n".'<a id="letter_'. $term->let .'"></a>';
          if ($separate) {
            $output .= '<h2 class="lexicon-letter">'. drupal_strtoupper($term->let) .'</h2>';
          }
          $output .= '<dl>';
          $current_let = $term->let;
        }
        $output .= theme('lexicon_overview_item', $vid, $term, $show_desc, $dest);
      }
      $notfirst = TRUE;
    }
    //Close the last <dl>
    $output .= '</dl>';
    if ($separate && variable_get('lexicon_go_to_top_link', FALSE) == TRUE) {
      $output .= '<p><a href="#' . variable_get('lexicon_go_to_top_link_fragment', 'top') . '" title="' . t('Go to top') . '" class="lexicon_to_top_link">' . t('Go to top') . '</a></p>';
    }
  }
  $output .= '</div></div>';
  return lexicon_admin_links($vocab, $dest) . $output;
}

function theme_lexicon_overview_item($vid, $term, $show_desc = TRUE, $destination = NULL) {
  global $base_url;
  static $click_option, $link_related, $show_detailed, $one_way, $tax_img_avail, $access_tax, $access_search, $show_edit, $show_search, $page_per_letter;
  if (!isset($click_option)) {
    $click_option = variable_get('lexicon_click_option', 0);
    $link_related = variable_get('lexicon_link_related', TRUE);
    $show_detailed = variable_get('lexicon_show_detailed', FALSE);
    $show_edit = variable_get('lexicon_show_edit', TRUE);
    $show_search = variable_get('lexicon_show_search', TRUE);
    $one_way = variable_get('lexicon_link_related_how', FALSE);
    $tax_img_avail = module_exists('taxonomy_image');
    $access_tax = user_access('administer taxonomy');
    $access_search = user_access('search content');
    $page_per_letter = variable_get('lexicon_page_per_letter', FALSE);
  }
  $path = variable_get('lexicon_path_' . $vid, 'lexicon/' . $vid);

  if ($tax_img_avail && $show_desc) {
    $img = taxonomy_image_display($term->tid);
    if ($img) {
      $obj = taxonomy_image_get_object($term->tid);
      $img = '<a href="'. $obj->url .'">'. $img .'</a>';
    }
  }
  else {
    $img = NULL;
  }

  $output .= '<dt class="depth'. $term->depth .'">'. $img;

  //Make sure the id of the anchor is a valid one and doe not contain illegal characters or format
  $allowed_chars = '-A-Za-z0-9._:';
  $id = _lexicon_create_valid_id($term->name);
  $output .= '<a id="' . $id . '"></a>';

  if ($show_desc) {
    $output .= str_repeat('--', $term->depth) . filter_xss_admin($term->name);
  }
  else {
    $output .= l($term->name, $path . '/term/'. $term->tid) .' ';
  }

  if ($show_edit && $access_tax) {
    $output .= ' '. l(t('edit term'), 'admin/content/taxonomy/edit/term/'. $term->tid,
      array('attributes' => array('class' => 'lexicon-edit-term', 'title' => t('edit this term and definition')), 'query' => $destination)
      );
  }

  if ($show_search && $access_search) {
    $output .= l(t('search for term'), 'search/node/"'. $term->name .'"',
      array('attributes' => array('class' => 'lexicon-search-term', 'title' => t('search for content using this term')))
      );
  }
  $output .= '</dt>';

  if ($show_desc) {
    if ($term->description || $term->synonyms) {
      $output .= '<dd class="depth' . $term->depth . '"><p>' . htmlspecialchars($term->description) . '</p>';
      if ($relations = lexicon_get_related($term->tid, 'name', $one_way)) {
        $output .= "<p><span class=\"lexicon-related\">". t("See also") .": ";
        foreach ($relations as $related) {
          if ($link_related) {
            if ($click_option == 1) {
              $fragment = _lexicon_create_valid_id($related->name);
              if ($page_per_letter) {
                $items[] .= l($related->name, $path . '/letter_' . drupal_strtolower(drupal_substr($related->name, 0, 1)) , array('fragment' => $fragment));
              }
              else {
                $items[] .= l($related->name, $path , array('fragment' => $fragment));
              }
            }
            else {
              $items[] .= l($related->name, 'taxonomy/term/'. $related->tid);
            }
          }
          else {
            $items[] .= check_plain($related->name);
          }
        }
        $output .= implode(', ', $items) ."</span></p>\n";
        unset($items);
      }
      if ($term->synonyms) {
        $output .= '<p><span class="lexicon-synonyms">'. t('Synonyms') . ': ' . implode(', ', $term->synonyms) ."</span></p>\n";
    }
      $output .= '</dd>';
    }

    $detailed_exists = db_result(db_query(db_rewrite_sql('SELECT COUNT(t.nid) FROM {term_node} t JOIN {node} n USING (nid) WHERE t.tid=%d AND n.status=1'), $term->tid));
    if ($detailed_exists) {
      // Do we want to show the teasers?
      if ($show_detailed) {
        $output .= '<div class="lexicon-detailed">';
        $text = NULL;
        $detailed = db_query(db_rewrite_sql('SELECT t.nid FROM {term_node} t JOIN {node} n USING (nid) WHERE t.tid=%d AND n.status=1'), $term->tid);
        while ($row = db_fetch_array($detailed)) {
          $node = node_load($row['nid']);
          // Format as teaser view with links.
          $text .= node_view($node, TRUE, FALSE, TRUE);
        }
        if ($text) {
          $fieldset = array(
            '#title' => t('Detailed definition of @term', array('@term' => $term->name)),
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
            '#value' => $text,
            );
          $output .=  theme('fieldset', $fieldset);
        }
        $output .= "</div>\n";
      }
      else {
        $output .= '<div class="lexicon-detailed-link">'.
          l(t('Detailed definition of @term', array('@term' => $term->name)), taxonomy_term_path($term))
          .'</div>';
      }
    }
  }
  return decode_entities($output);
}

function theme_lexicon_block_term($vid, $term, $link = TRUE) {
  $path = variable_get('lexicon_path_' . $vid, 'lexicon/' . $vid);
  global $base_url;
  static $click_option, $link_related, $one_way, $tax_img_avail;
  if (!isset($click_option)) {
    $click_option = variable_get('lexicon_click_option', 0);
    $link_related = variable_get('lexicon_link_related', TRUE);
    $one_way = variable_get('lexicon_link_related_how', FALSE);
    $tax_img_avail = module_exists('taxonomy_image');
  }

  if ($tax_img_avail) {
    $img = taxonomy_image_display($term->tid);
    if ($img) {
      $obj = taxonomy_image_get_object($term->tid);
      $img = '<a href="'. $obj->url .'">'. $img .'</a>';
    }
  }
  else {
    $img = NULL;
  }

  $output .= '<div class="lexicon-block-term-name">';

  if ($link) {
    if (variable_get('lexicon_page_per_letter', FALSE)) {
      $output .= l($term->name, $path  .'/letter_'. drupal_strtolower(drupal_substr($term->name, 0, 1)), array('fragment' => _lexicon_create_valid_id($term->name)));
    }
    else {
      $output .= l($term->name, $path, array('fragment' => _lexicon_create_valid_id($term->name)));
    }
  }
  else {
    $output .= check_plain($term->name);
  }

  $output .= "</div>\n";

  $output .= '<div class="lexicon-block-term-description">';
  $output .= $term->description ? check_markup($term->description) : NULL;
  $output .= "</div>\n";

  if ($relations = lexicon_get_related($term->tid, 'name', $one_way)) {
    $output .= '<span class="lexicon-related">'. t('See also') .': ';
    $items = array();
    foreach ($relations as $related) {
      if ($link_related) {
        if ($click_option == 1) {
          $items[] .= l($related->name, $path . '/'. $term->vid, array('fragment' => 'term'. $related->tid));
        }
        else {
          $items[] .= l($related->name, $path . '/term/'. $related->tid);
        }
      }
      else {
        $items[] .= check_plain($related->name);
      }
    }
    $output .= implode(', ', $items) ."</span>\n";
  }

  if ($term->synonyms) {
    $output .= '<span class="lexicon-synonyms"><strong>'. t('Synonyms') .'</strong>: ';
    $output .= implode(', ', $term->synonyms) ."</span>\n";
  }

  return decode_entities($output);
}

function lexicon_admin_links($vocabulary, $destination) {
  $output = '<div class="lexicon-admin-links">';
  $links = array();
  $url = 'admin/content/taxonomy/'. $vocabulary->vid;
  if ($router_item = menu_get_item($url .'/add/term')) {
    if ($router_item['access']) {
      $links['lexicon_add_term'] = array(
        'title' => t('Add term'),
        'href' => $url .'/add/term',
        'attributes' => array('class' => 'btn btn-primary icon-plus'),
        );
      $links['lexicon_list'] = array(
        'title' => t('List of terms'),
        'href' => $url,
        'attributes' => array('class' => 'btn btn-info icon-apply'),
        );
    }
  }
  if (user_access('administer taxonomy')) {
    $links['lexicon_edit'] = array(
      'title' => decode_entities(t('Edit @name', array('@name' => t(drupal_strtolower($vocabulary->name))))),
      'href' => 'admin/content/taxonomy/edit/vocabulary/'. $vocabulary->vid,
      'query' => $destination,
      );
  }
  if (user_access('administer filters')) {
    $links['lexicon_admin'] = array(
      'title' => t('Lexicon settings'),
      'href' => 'admin/settings/lexicon',
      'query' => $destination,
      );
  }

  if (!empty($links)) {
    $output .= theme('links', $links);
  }
  return $output .'</div>';
  }

function _lexicon_list() {
  $output = "";
  $destination = drupal_get_destination();
  $vids = variable_get('lexicon_vids', array());

  $vocs = array();
  foreach ($vids as $vid) {
    $voc = taxonomy_vocabulary_load($vid);
    $vocs[$voc->name] = $voc;
  }
  uksort($vocs, _lexicon_cmp_strcase);

  $eo = array('even', 'odd');
  $i = 0;
  $output .= '<table><tr><th>'. t("Lexicon name") .'</th><th>'. t('Operations') .'</th></tr>';
  foreach ($vocs as $voc) {
    $links = array();
    $class = $eo[++$i & 1];
    if (user_access('administer taxonomy')) {
      $links['lexicon_edit'] = array(
        'title' => t('Edit @name', array('@name' => t(drupal_strto($vocabulary->name)))),
        'href' => 'admin/content/taxonomy/edit/vocabulary/'. $voc->vid,
        'query' => $destination,
        );
    }
    $links['lexicon_view'] = array(
      'title' => t('List'),
      //'href' => 'lexicon/'. $voc->vid,
      'href' => variable_get('lexicon_path_' . $voc->vid, 'lexicon/' . $voc->vid),
      );
    if ($voc->description) {
      $output .= '<tr class="'. $class .' merge-down"><th>'. $voc->name .'</th><td>'. theme('links', $links) .'</td></tr>';
      $output .= '<tr class="'. $class .' merge-up"><td colspan="2" class="lexicon-list-description">'. $voc->description .'</td></tr>';
    }
    else {
      $output .= '<tr class="'. $class .'">';
      $output .= '<th>'. $voc->name .'</th><td>'. theme('links', $links) .'</td></tr>';
    }
  }

  // Were there any rows produced?
  if ($i == 0) {
    $output .= '<tr><td colspan="5">'. t('No applicable vocabularies were found, please check your settings.') .'</td></tr>';
  }
  $output .= '</table>';

  return $output;
}

function _lexicon_get_terms() {
  static $got = array();
  $show_all = variable_get('lexicon_allow_no_description', FALSE);
  $taxonomy_image_enabled = module_exists('taxonomy_image');

  if (!$got) {
    $terms = array();
    $synonyms = array();
    $vids = variable_get("lexicon_vids", 0);

    foreach ($vids as $vid) {
      $synonyms = _lexicon_get_synonyms($vid);

      // Get all lexicon terms and attach synonyms.
      // Omit terms without a description. those are usually container terms.
      // If multilingual taxonomy is enabled only show terms in current or no language
      if (module_exists('i18ntaxonomy')) {
        global $language;
        $result = db_query(db_rewrite_sql("SELECT t.tid, t.name, t.description, COUNT(tn.nid) as nodes FROM {term_data} t LEFT JOIN {term_node} tn USING(tid) WHERE t.vid=%d AND t.language in ('', '%s') GROUP BY t.tid, t.name, t.description ORDER BY LENGTH(t.name) DESC", 't', 'tid'), $vid, $language->language);
      }
      else {
        $result = db_query(db_rewrite_sql("SELECT t.tid, t.name, t.description, COUNT(tn.nid) as nodes FROM {term_data} t LEFT JOIN {term_node} tn USING(tid) WHERE t.vid=%d GROUP BY t.tid, t.name, t.description ORDER BY LENGTH(t.name) DESC", 't', 'tid'), $vid);
      }
      while ($term = db_fetch_object($result)) {
        if ($term->nodes) {
          // If there were any nodes attached, we need to see if they were unpublished.
          $unpubs = db_result(db_query(db_rewrite_sql("SELECT COUNT(n.nid) FROM {term_node} tn JOIN {node} n USING (nid) WHERE tn.tid=%d AND n.status=0"), $term->tid));
          $term->nodes -= $unpubs;
        }
        if ($term->description) {
          $term->description = htmlspecialchars($term->description, ENT_QUOTES);
        }
        if ($term->description || $show_all) {
          $term->synonyms = $synonyms[$term->tid];
          $term->synonyms[] = filter_xss($term->name);
          $term->vid = $vid;
          $terms[] = $term;
        }
        if ($taxonomy_image_enabled) {
          $term->image = taxonomy_image_display($term->tid);
        }
        else {
          $term->image = NULL;
        }
      }
    }
    $got = $terms;
  }
  else {
    // Use the already loaded terms
    $terms = $got;
  }
  return $terms;
}

/**
 * Find all term objects related to a given term ID.
 * Adapted from taxonomy.module.
 *
 * @param $tid
 *   the term id to look up (int).
 * @param $one_way
 *   whether to do one-way or two-way relations (bool).
 * @return
 *   an array related-tid => related-name
 *
 */
function lexicon_get_related($tid, $key = 'tid', $one_way = FALSE) {
  if ($tid) {
    $related = array();
    $qargs = array_fill(0, 3, $tid);

    if ($one_way) {
      $result = db_query('SELECT r.tid2 FROM {term_relation} r WHERE r.tid1 = %d ORDER BY r.tid2', $qargs);
      while ($term = db_fetch_object($result)) {
        // Hope that taxonomy has this cached to save a query.
        $rel = taxonomy_get_term($term->tid2);
        $related[$rel->$key] = $rel;
      }
    }
    else {
      // Two-way (normal taxonomy function).
      $result = db_query('SELECT t.*, tid1, tid2 FROM {term_relation}, {term_data} t WHERE (t.tid = tid1 OR t.tid = tid2) AND (tid1 = %d OR tid2 = %d) AND t.tid != %d ORDER BY weight, name', $qargs);
      while ($term = db_fetch_object($result)) {
        $related[$term->$key] = $term;
      }
    }

    return $related;
  }
  else {
    return array();
  }
}

// Get all synonyms for all lexicon terms.
function _lexicon_get_synonyms($vid) {
  $result = db_query(db_rewrite_sql('SELECT ts.tid, ts.name FROM {term_synonym} ts JOIN {term_data} t USING(tid) WHERE t.vid=%d', 't', 'tid'), $vid);
  while ($synonym = db_fetch_object($result)) {
    $synonyms[$synonym->tid][] = filter_xss($synonym->name);
  }
  return $synonyms;
}

function _lexicon_is_boundary($char) {
  if (extension_loaded('mbstring')) {
    return (mb_strpos("!\"#\$%&'()*+,-./:;<=>?@[\]^_`{|}~� ��������� \t\n\r", $char) !== FALSE);
  }
  else {
    return (strpos("!\"#\$%&'()*+,-./:;<=>?@[\]^_`{|}~� ��������� \t\n\r", $char) !== FALSE);
  }
}

// Natively only available in PHP 5+
// WARNING: Eats a tremendous amount of memory!
if (!function_exists("stripos")) {
  function stripos($haystack, $needle, $offset = 0) {
    return strpos(drupal_strtoupper($haystack), drupal_strtoupper($needle), $offset);
  }
}
if (!function_exists("mb_stripos")) {
  function mb_stripos($haystack, $needle, $offset = 0) {
    return mb_strpos(drupal_strtoupper($haystack), drupal_strtoupper($needle), $offset);
  }
}

function _lexicon_clear_filter_cache($vid = NULL, $force = FALSE) {
  // Only clear the filter cache if the vocabulary is used as a Lexicon or when the clearing of the filter cache is forced (from the admin form submit).
  if (in_array($vid, $vids = variable_get('lexicon_vids', array())) || $force ) {
    // We could throw less things away if we checked which filter formats
    // used the lexicon filter, and we only threw those away. In practice,
    // most if not all formats would use the lexicon filter, so we just
    // get rid of them all.
    cache_clear_all('*', 'cache_filter', TRUE);
    drupal_set_message(t('The filter cache has been cleared. There may be a temporary performance degradation while it is rebuilt.'));
  }
}

function _lexicon_clear_menu_cache() {
  menu_rebuild();
  drupal_set_message(t('The menu cache has been cleared. There may be a temporary performance degradation while it is rebuilt.'));
}

function _lexicon_cmp_strcase($a, $b) {
  return strcmp(drupal_strtolower($a), drupal_strtolower($b));
}

function _alphabar_instruction_default() {
  if (variable_get('lexicon_page_per_letter', FALSE)) {
    return t('Click one of the letters above to be taken to a page of all terms beginning with that letter.');
  }
  else {
    return t('Click one of the letters above to advance the page to terms beginning with that letter.');
  }
}

function _lexicon_create_valid_id($name) {
  $allowed_chars = '-A-Za-z0-9._:';
  $id = preg_replace(
    array(
      '/&nbsp;|\s/',
      '/\'/',
      '/&mdash;/',
      '/&amp;/',
      '/&[a-z]+;/',
      '/[^' . $allowed_chars . ']/',
      '/^[-0-9._:]+/',
      '/__+/',
    ),
    array(
      '_',     // &nbsp; and spaces
      '-',     // apostrophe, so it makes things slightly more readable
      '--',    // &mdash;
      'and',   // &amp;
      '',      // any other entity
      '',      // any character that is invalid as an ID name
      '',      // any digits at the start of the name
      '_',     // reduce multiple underscores to just one
    ),
    strip_tags($name)
    );
  return $id;
}
