? .DS_Store
Index: CHANGELOG
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/glossary/CHANGELOG,v
retrieving revision 1.12
diff -u -F^f -r1.12 CHANGELOG
--- CHANGELOG	11 Aug 2004 12:36:19 -0000	1.12
+++ CHANGELOG	2 Oct 2004 03:23:56 -0000
@@ -1,3 +1,7 @@
+01 Oct 2004
+[Craig Courtney - ccourtne]
+- Update to use nodes instead of taxonomy
+
 11 Aug 2004
 [Alastair Maw - Al]
 - Update to work with new filter system.
Index: README
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/glossary/README,v
retrieving revision 1.4
diff -u -F^f -r1.4 README
--- README	6 Feb 2004 23:18:27 -0000	1.4
+++ README	2 Oct 2004 03:23:56 -0000
@@ -14,13 +14,6 @@
 glossary or directly to the detailed description of the term, if
 available.
 
-The glossary uses Drupal's built in taxonomy feature, so you can organize
-your terms in a Drupal vocabulary. This allows you to create hierarchical
-structures, synonyms and relations. Glossary terms are represented with
-the taxonomy terms in the glossary vocabulary. Descriptions are used to
-provide a short explanation of the terms. You can attach nodes to the
-terms to provide detailed explanation on the keywords.
-
 This module also works with nicelinks.module, which will give you pretty
 hover-over glossary term descriptions on reasonably modern browsers
 (while degrading properly on older ones).
@@ -39,28 +32,19 @@
    Drupal should automatically detect it. Enable the module on
    the modules' administration page.
 
-2. Glossary terms are managed as a vocabulary within the taxonomy.module.
-   To get started with glossary, create a new vocabulary on the
-   taxonomy administration page. The vocabulary need not be associated
-   with any modules, though you can attach detailed description to terms
-   by adding nodes to the terms, so it might be a good idea to associate
-   the vocabulary with the "story" module. Add a few terms to the vocabulary.
-   The term title should be the glossary entry, the description should be
-   the explanation of that term. You can make use of the hierarchy,
-   synonym, and related terms features.
-
-3. Go to the glossary filter administration page under the filters
-   administration menu, and associate the glossary module with the
-   vocabulary you created above. You can also change other options
-   on that page.
+2. Add the glossary tables to your Drupal database using the glossary.mysql
+   file.  Normally this will look like this:
+   mysql -u drupal -p drupal < glossary.mysql
+
+3. To get started with glossary, create a new glossary term from the create
+   menu. The node title should be the glossary entry
+
+3. Add the glossary filter to you input format.  Setting for the glossary
+   links can be set in the input format filter configuration screens.
    
 4. If you would like to use the glossary icon, a default one is included
    with this module. 
    
-5. Ensure that the _head() hooks are called in the theme you use. For
-   Drupal default themes this should not be a problem. Your own theme
-   might need a bit of modification.
-
 Authors
 -------
 
Index: TODO
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/glossary/TODO,v
retrieving revision 1.7
diff -u -F^f -r1.7 TODO
--- TODO	21 Feb 2004 12:37:39 -0000	1.7
+++ TODO	2 Oct 2004 03:23:56 -0000
@@ -7,6 +7,3 @@
   (perhaps any sequence of 3 or more capital letters) and save those
   in a table where the admin "promote" them to glossary term status and
   give them definitions if they are in fact worthy of glossary status.
-
-  - change backend storage to 'node' instead of 'term' so that entries may be submitted by anyone, and not just
-  taxonomy admins. further, enttries could then be queued, promoted to home page, versioned, etc.
\ No newline at end of file
Index: glossary.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/glossary/glossary.module,v
retrieving revision 1.66
diff -u -F^f -r1.66 glossary.module
--- glossary.module	13 Sep 2004 18:02:09 -0000	1.66
+++ glossary.module	2 Oct 2004 03:23:56 -0000
@@ -1,412 +1,380 @@
-<?php
-// $Id: glossary.module,v 1.66 2004/09/13 18:02:09 unconed Exp $
-
-function glossary_help($section = "admin/help#glossary") {
-  $output = '';
-
-  switch ($section) {
-    case 'admin/help#glossary':
-      return t('
-<p>Glossary helps newbies understand the jargon which always crops up when specialists talk about a topic. Doctors discuss CBC and EKG and CCs. Web developers keep talking about CSS, P2P, XSLT, etc. This is all intimidating for newbies.</p>
-<p>Glossary.module scans posts for glossary terms (and their synonyms) in the body. If found, the glossary 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 glossary.</p>
-<p>Glossary terms are managed as a vocabulary within the taxonomy.module. To get started with glossary, create a new vocabulary on the <a href="%taxonomy_admin">taxonomy administration</a> 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 glossary entry and the description should be its definition. You can make use of the hierarchy, synonym, and related terms features. These features impact the display of the glossary when viewed in an overview. Next, enable the glossary module, associate the vocabulary with the glossary and optionally change the other settings at the <a href="%glossary_settings">glossary settings</a> page.</p>
-<p>Administration of glossary requires <em>%permissions</em> permissions.</p>', array("%permissions" => join(", ", array(t("administer glossary"), t("administer taxonomy"), t("access administration pages"))), "%taxonomy_admin" => url('admin/taxonomy'), "%glossary_settings" => url('admin/filters/glossary')));
-      break;
-    case 'admin/modules#description':
-      return t('Maintain a glossary on your site.');
-      break;
-    case 'filter#long-tip':
-    case 'filter#short-tip':
-      return t('<a href="%glossary_page">Glossary</a> terms will be automatically marked with links to their descriptions', array("%glossary_page" => url('glossary')));
-  }
-}
-
-// Define this as an empty function in your theme, if your CSS
-// includes these styles, and you don't need to add it on all
-// pages in the HTML head
-function theme_glossary_html_head($pageid = 'filtered-text') {
-  switch($pageid) {
-    case "filtered-text":
-      return "<style type=\"text/css\">
-        .glossary_indicator { vertical-align: super; cursor: help; font-size: xx-small; padding-left: 2px; font-weight: bold }
-        a.glossary_term { text-decoration: none; border-bottom: 1px dotted #080; color: #080; cursor: help; }
-</style>";
-    
-    case "glossary-page":
-      return "<style type=\"text/css\">
-        dt { font-weight: bold; }
-        dd { font-weight: normal; font-size: x-small; margin-left: 15px; }
-        .edit-term { font-size: x-small; font-weight: normal; padding-left: 6px;} 
-        .glossary-related { font-style: italic; }
-	.glossary-links { text-align: center; letter-spacing: -0.05em; }
-</style>";
-  }
-}
-
-function glossary_perm() {
-  return array("administer glossary");
-}
-
-function glossary_menu() {
-  $access = user_access('administer glossary');
-  $items = array();
-  $items[] = array(
-    'path' => 'admin/filters/glossary',
-    'title' => t('glossary'),
-    'callback' => 'glossary_admin',
-    'access' => $access);
-  $items[] = array(
-    'path' => 'admin/filters/glossary/editterms',
-    'title' => t('edit terms'),
-    'callback' => 'taxonomy_admin',
-    'access' => $access);
-  $items[] = array(
-    'path' => 'glossary',
-    'title' => t('glossary'),
-    'callback' => 'glossary_page',
-    'access' => user_access('access content'),
-    'type' => MENU_SUGGESTED_ITEM);
-  $items[] = array(
-    'path' => 'glossary/',
-    'title' => t('view'),
-    'callback' => 'glossary_page',
-    'access' => user_access('access content'),
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10);
-  $items[] = array(
-    'path' => 'glossary/edit',
-    'title' => t('edit'),
-    'callback' => 'taxonomy_admin',
-    'access' => user_access('access content'),
-    'type' => MENU_LOCAL_TASK,
-    'access' => $access);
-  return $items;
-}
-
-function glossary_admin() {
-
-  system_settings_save();
-  
-  // Clear filter cache if used on this site
-  if ($_POST['op'] && module_exist('filtercache')) {
-    filtercache_clear();
-  }
-  
-  $options[0] = t('<none>');
-  foreach (taxonomy_get_vocabularies() as $vocabulary) {
-    $options[$vocabulary->vid] = $vocabulary->name;
-  }
-  
-  $group1 = form_select(t("Select Vocabulary"), "glossary_vid", variable_get("glossary_vid", 0), $options, t("Select a vocabulary which holds all terms for your glossary ... When enabled, posts will be scanned for glossary terms. An icon or a superscripted link is inserted beside each term. Hover over the icon or link to learn the meaning of that term."));
-  $group1 .= form_select(t("Match type"), "glossary_match", variable_get("glossary_match", "match"), array("b" => t("word"), "lr" => t("right or left substring"), "l" => t("left substring"), "r" => t("right substring"), "s" => t("any substring")), t("Choose the match type of glossary links."));
-  $group1 .= form_select(t("Case sensitivity"), "glossary_case", variable_get("glossary_case", "1"), array(t("case insensitive"), t("case sensitive")), t("Match either case sensitive or not. Case sensitive matches are far not that resource intensive."));
-  $group1 .= form_select(t("Replace matches"), "glossary_replace_all", variable_get("glossary_replace_all", 0), array(t("only the first match"), t("all matches")), t("Whether only the first match should be replaced or all matches."));
-  $output .= form_group(t("General settings"), $group1);
-  
-  $group2 = form_select(t("Term Indicator"), "glossary_replace", variable_get("glossary_replace", "superscript"), array("superscript" => t("superscript"), "icon" => t("icon"), "acronym link" => t("replace with acronym link")), t("Display terms using either a superscript formatted link, an icon, or an &lt;acronym&gt; tag."));
-  $group2 .= form_textfield(t("Superscript"), "glossary_superscript", variable_get("glossary_superscript", "i"), 15, 255, t("If you choose %superscript above, enter the superscript text.",array("%superscript" => "<strong>" . t("superscript") . "</strong>")));
-  $group2 .= form_textfield(t("Glossary Icon URL"), "glossary_icon", variable_get("glossary_icon", "glossary.gif"), 50, 255, t("If you choose %icon above, enter the URL of the glossary icon relative to the root of your Drupal site.", array("%icon" => "<strong>" . t("icon") . "</strong>")));
-  $output .= form_group(t("Indicator settings"), $group2);
-  
-  print theme("page", system_settings_form($output));
-}
-
-function glossary_taxonomy($op, $type, $object) {
-  if (module_exist('filtercache') && ($object->vid == variable_get("glossary_vid", 0))) {    
-    filtercache_clear();
-  }
-}
-
-function _glossary_filter_settings() {
-  return form_group(t("Glossary filter"), t('The glossary filter module is loaded. See the separate <a href="%glossary_settings">glossary settings</a> page for the available options.', array("%glossary_settings" => url("admin/filters/glossary"))));
-}
-
-function glossary_filter_tips($delta, $format, $long = false) {
-  return $long ? glossary_help('filter#long-tip') : glossary_help('filter#short-tip');
-}
-
-function glossary_filter($op, $delta = 0, $format = -1, $text = "") {
-  switch ($op) {
-    case "list":
-      return array(0 => t("Glossary filter"));
-    case "description":
-      return glossary_help('admin/modules#description');
-    case "process":
-      return _glossary_filter_process($text);
-    case "settings":
-      return _glossary_filter_settings($text);
-    default:
-      return $text;
-  }
-}
-
-function _glossary_filter_process($text) {
-  static $css_inserted = FALSE;
-  
-  if (variable_get("glossary_vid", 0)) {
-    // Inject the CSS required to the HTML header the first time this filter runs
-    if (!$css_inserted) {
-      drupal_set_html_head(theme("glossary_html_head", 'filtered-text'));
-      $css_inserted = TRUE; 
-    }
-    
-    $text = " ". $text ." ";
-    $replace_mode = variable_get("glossary_replace", "superscript");
-    $terms = _glossary_get_terms();
-    
-    foreach ($terms as $term) {
-      $term_title = str_replace("\\", "\\\\", drupal_specialchars($term->name. ": ". strip_tags($term->description)));
-      $linkto     = ($term->nodes > 0) ? "glossary/$term->tid" : "glossary#term$term->tid";
-      $ins_before = $ins_after = '';
-      
-      switch ($replace_mode) {
-        case "superscript":
-          $ins_after = l(variable_get("glossary_superscript", "i"), $linkto, $attribs);
-          break;
-        case "acronym link":
-          $ins_before = '<a class="glossary_term" href="' . url($linkto) . '">';
-          $ins_before .= "<acronym title=\"$term_title\">";
-          $ins_after = '</acronym></a>';
-          break;
-        default:
-          unset($attribs['class']);
-          $img = "<img src=\"". variable_get("glossary_icon", "glossary.gif"). "\" />";
-          $ins_after = l($img, $linkto, $attribs);
-          break;
-      }
-      
-      // replace term and synonyms with the desired new HTML code
-      foreach ($term->synonyms as $candidate) {
-        $text = _glossary_insertlink($text, $candidate, $ins_before, $ins_after);
-      }
-    }
-  }
-  return $text;
-}
-
-/**
-* Insert glossary links to $text after every $match that is not inside a link.
-* $ins_before is prepended to the matches, $_insafter is appended to them.
-* Match type and replace mode all depend on user settings.
-*
-* TODO: improve performance with not keeping *2.5 copies* of the string in memory: 
-*         $text                 - original
-*         $newtext              - transformed
-*         $before . $this_match - for checking stuff
-*/
-function _glossary_insertlink(&$text, $match, $ins_before, $ins_after) {
-
-  $findfunc = (variable_get("glossary_case", "1") ? "strpos" : "stripos");
-  $next = $findfunc($text, $match);
-  
-  if ($next === FALSE) { // no match at all
-    return $text;
-  }
-  else { // at least one match
-    $prevend    = 0;
-    $newtext    = '';
-    $matchlen   = strlen($match);
-    $textlen    = strlen($text);
-    $replaceall = variable_get("glossary_replace_all", 0);
-    
-    while ($next && ($next <= $textlen)) {
-      
-      // get parts of the match for further investigation
-      $before     = substr($text, 0, $next);
-      $this_match = substr($text, $next, $matchlen);
-      
-      // see if we have a proper match or not
-      $open  = substr_count($before, "<");
-      $close = substr_count($before, ">");
-      $opena  = substr_count($before, "<a ");
-      $closea = substr_count($before, "</a>");
-      $openacro  = substr_count($before, "<acronym");
-      $closeacro = substr_count($before, "</acronym>");
-      $proper_match = FALSE;
-      if ($opena <= $closea && $open <= $close && $openacro <= $closeacro) { // Not in an open link
-        switch (variable_get("glossary_match", "b")) {
-          case "lr": // require word break left or right
-              $proper_match = (_glossary_is_boundary($text{$next-1}) ||
-                               _glossary_is_boundary($text{$next+$matchlen}));
-              break;
-          case "b": // require word break left and right
-              $proper_match = (_glossary_is_boundary($text{$next-1}) &&
-                               _glossary_is_boundary($text{$next+$matchlen}));
-              break;
-          case "l":  // require word break left
-              $proper_match = _glossary_is_boundary($text{$next-1});
-              break;
-          case "r": // require word break right
-              $proper_match = _glossary_is_boundary($text{$next+$matchlen});
-              break;
-          case "s": // match any substring
-          default:
-              $proper_match = TRUE;
-              break;
-        }
-      }
-      
-      if ($proper_match) { // found match
-        $newtext .= substr($text, $prevend, ($next-$prevend)) . $ins_before . $this_match . $ins_after;
-        if ($replaceall == 0) { return $newtext . substr($text, $next + $matchlen); }
-      }
-      else { // not applicable match
-        $newtext .= substr($text, $prevend, ($next-$prevend)) . $this_match;
-      }
-      
-      // Step further in finding the next match
-      $prevend = $next + $matchlen;
-      $next = $findfunc($text, $match, $prevend);
-    }
-    // Append remaining part
-    return $newtext . substr($text, $prevend);
-  }
-}
-
-function glossary_page() {
-  $tid = intval(arg(1));
-  if (!$tid) {
-    drupal_set_html_head(theme("glossary_html_head", 'glossary-page'));
-    print theme("page", glossary_overview(), t("Glossary"));
-  }
-  else {
-    $taxonomy->operator = 'or';
-    $taxonomy->str_tids = $taxonomy->tids = $tid;
-    $term = taxonomy_get_term($tid);
-    $breadcrumb = array(
-      l(t("Home"), NULL),
-      l(t("Glossary"), "glossary")
-    );
-    $contents = taxonomy_render_nodes(taxonomy_select_nodes($taxonomy));
-    drupal_set_html_head(theme("glossary_html_head", 'glossary-page'));
-    print theme("page", $contents, $term->name, $breadcrumb);
-  }
-}
-
-function _glossary_alphabar() {
-  $output = "\n<div class=\"glossary-links\">";
-  foreach (range('a', 'z') as $letter) {
-    $letters[] = l($letter, 'glossary', NULL, NULL, 'letter' . $letter);
-  }
-  $output .= theme_links($letters);
-  $output .= "</div>\n";
-  return $output;
-}
-
-
-function _glossary_anchor($lower, $upper) {
-  foreach (range($lower, $upper) as $letter) {
-    $output .= "<a id=\"letter$letter\"></a>\n";
-  }
-  return $output;
-}
-
-function glossary_overview() {
-  global $tree;
-
-  if ($vid = variable_get("glossary_vid", 0)) {
-    $output = _glossary_alphabar();
-    $tree = taxonomy_get_tree($vid);
-    if ($tree) {
-      $output .= _glossary_anchor('a', strtolower($tree[0]->name[0]));
-      foreach ($tree as $term) {
-        // skip this on first iteration since was already outputted above
-        if (isset($lastletter)) {
-          $firstletter = strtolower($term->name[0]);
-          $output .= _glossary_anchor(chr(ord($lastletter)+1) , $firstletter);
-        }
-        $output .= "<a id=\"term{$term->tid}\"></a>\n" .
-                   '<dl class="glossary-item" style="margin-left:' . ($term->depth*15) . "px;\">\n";
-        $output .= "<dt>{$term->name}";
-        if (user_access('administer taxonomy')) {
-          $output .= l(t('edit term'), "admin/taxonomy/edit/term/$term->tid", array('title' => t('edit this term and definition.'), 'class' => 'edit-term'));
-        }
-        $output .= "</dt><dd>{$term->description} \n";
-        if ($nodes = taxonomy_term_count_nodes($term->tid)) {
-          $output .= '<p>' . l(t('Detailed description'), "taxonomy/term/{$term->tid}") . '</p>';
-        }
-        if ($relations = taxonomy_get_related($term->tid, "name")) {
-          $output .= "<span class=\"glossary-related\">". t("See also") . ": ";
-          foreach ($relations as $related) {
-            $items[] .= l($related->name, 'glossary', NULL, NULL, $related->tid);
-          }
-          $output .= implode(', ', $items) . "</span>\n"; // strip trailing comma
-          unset($items);
-        }
-        $output .= "</dd></dl>\n";
-        $lastletter = strtolower($term->name[0]);
-      }
-      $output .= _glossary_anchor(chr(ord($lastletter)+1), 'z');
-    }
-    if (user_access('administer taxonomy')) {
-      $links[] = l(t('add term'), "admin/taxonomy/add/term/$vid");
-      $links[] = l(t('edit glossary'), 'admin/taxonomy');
-      $output .= theme('links', $links);
-    }
-    return $output;
-  }
-  elseif (user_access("administer glossary") && user_access("administer taxonomy") && user_access("access administration pages")) {
-    return t("You must designate a vocabulary to hold glossary entries. See the %glossary_admin page.", array ("%glossary_admin" => l(t("glossary administration"), "admin/filters/glossary")));
-  } else {
-    drupal_set_message('The administrator must assign a vocabulary to the glossary') ;
-  }
-}
-
-function glossary_help_page() {
-  print theme('page', glossary_help('admin/help#glossary'));
-}
-
-function _glossary_get_terms() {
-  static $terms = FALSE;
-  
-  if ($terms === FALSE) {
-    $terms = $synonyms = array();
-    $vid = variable_get("glossary_vid", 0);
-    
-    $synonyms = _glossary_get_synonyms();
-    
-    // Get all glossary terms and attach synonyms.
-    // omit terms without a description. those are usually container terms.
-    $result = db_query("SELECT t.name, t.description, t.tid, COUNT(tn.nid) as nodes FROM {term_data} t LEFT JOIN {term_node} tn ON t.tid = tn.tid WHERE t.vid = %d GROUP BY t.tid, t.name, t.description ORDER BY LENGTH(t.name) DESC", $vid);
-    while ($term = db_fetch_object($result)) {
-      if ($term->description) {
-        $term->synonyms = $synonyms[$term->tid];
-        $term->synonyms[] = $term->name;
-        $terms[] = $term;
-      }
-    }
-  }
-  
-  return $terms;
-}
-
-// Get all synonyms for all glossary terms
-function _glossary_get_synonyms() {
-  $vid = variable_get("glossary_vid", 0);
-  $result = db_query("SELECT ts.tid, ts.name FROM {term_synonym} ts, {term_data} t WHERE ts.tid = t.tid AND t.vid = %d", $vid);
-  while ($synonym = db_fetch_object($result)) {
-    $synonyms[$synonym->tid][] = $synonym->name;
-  }
-  return $synonyms;
-}
-
-// This seems to be 1.2 times faster in fine-grained testing then
-// the ereg solution used before. The chars used here are from the
-// grep info page.
-function _glossary_is_boundary($char) {
-  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(strtoupper($haystack), strtoupper($needle), $offset);
-  }
-}
-
-function glossary_trip_search_taxonomy($term) {
-  return l($term->name, "glossary", NULL, NULL, $term->tid). trip_search_teaser($term->description);
-}
-
-?>
+<?php
+// $Id: glossary.module,v 1.66 2004/09/13 18:02:09 unconed Exp $
+
+/**
+ * Implementation of hook_node_name().
+ */
+function glossary_node_name($node) {
+  return t('glossary term');
+}
+
+function glossary_perm() {
+  return array('create glossary term','edit own glossary term','administer glossary');
+}
+
+/**
+ * Implementation of hook_access().
+ */
+function glossary_access($op, $node) {
+  global $user;
+
+  if ($op == 'create') {
+    return user_access('create glossary term');
+  }
+
+  if ($op == 'update' || $op == 'delete') {
+    if (user_access('administer glossary') || (user_access('edit own glossary term') && ($user->uid == $node->uid))) {
+      return TRUE;
+    }
+  }
+}
+
+/**
+ * Implementation of hook_form().
+ */
+function glossary_form(&$node) {
+  $output = '';
+
+  if (function_exists('taxonomy_node_form')) {
+    $output .= implode('', taxonomy_node_form('glossary', $node));
+  }
+
+  $output .= form_textfield(t('Short Description'), 'teaser', $node->teaser, 60, 255, 'Enter a short description of the term.', NULL, TRUE);
+  $output .= form_textarea(t('Definition'), 'body', $node->body, 60, 20, 'The definition of the term entered in the title field.', NULL, TRUE);
+  $output .= filter_form('format', $node->format);
+  $output .= form_textarea(t('Synonyms'), 'editsynonyms', implode("\n",$node->synonyms), 20, 10, 'Synonyms which should also be linked to this glossary term, one synonym per line.');
+
+  return $output;
+}
+
+/**
+ * Implementation of hook_nodeapi()
+ */
+function glossary_nodeapi(&$node, $op, $teaser=NULL, $page=NULL) {
+  switch ($op) {
+    case 'validate':
+      $node->synonyms = array();
+      foreach (explode ("\n", str_replace("\r", '', $node->editsynonyms)) as $synonym) {
+        $node->synonyms[] = $synonym;
+      }
+      break;
+    case 'delete':
+      db_query('DELETE FROM {glossary_synonym} WHERE nid = %d', $node->nid);
+      cache_clear_all('filter:', true);
+      break;
+    case 'insert':
+    case 'update':
+      db_query('DELETE FROM {glossary_synonym} WHERE nid = %d', $node->nid);
+      if ($node->synonyms) {
+        foreach ($node->synonyms as $synonym) {
+          if ($synonym) {
+            db_query("INSERT INTO {glossary_synonym} (nid, synonym) VALUES (%d, '%s')", $node->nid, chop($synonym));
+          }
+        }
+        cache_clear_all('filter:', true);
+      }
+      break;
+    case 'load':
+      $result = db_query('SELECT synonym FROM {glossary_synonym} WHERE nid = %d', $node->nid);
+      while ($synonym = db_fetch_object($result)) {
+        $node->synonyms[] = $synonym->synonym;
+      }
+      break;
+  }
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function glossary_help($section = "admin/help#glossary") {
+  $output = '';
+
+  switch ($section) {
+    case 'admin/help#glossary':
+      return t('
+<p>Glossary helps newbies understand the jargon which always crops up when specialists talk about a topic. Doctors discuss CBC and EKG and CCs. Web developers keep talking about CSS, P2P, XSLT, etc. This is all intimidating for newbies.</p>
+<p>Glossary.module scans posts for glossary terms (and their synonyms) in the body. If found, the glossary 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 glossary.</p>');
+      break;
+    case 'admin/modules#description':
+      return t('Maintain a glossary on your site.');
+      break;
+    case 'node/add#glossary':
+      return t('Glossary terms helps newbies understand the jargon which always crops up when specialists talk about a topic. When a glossary term is found during display of a node a link will be created to the definition.');
+      break;
+  }
+}
+
+/**
+ * Implement hook_menu().
+ */
+function glossary_menu($may_cache) {
+  if ($may_cache) {
+
+    $access = user_access('administer glossary');
+
+    $items = array();
+
+    $items[] = array('path' => 'node/add/glossary', 'title' => t('glossary term'),
+      'access' => glossary_access('create', NULL));
+
+    $items[] = array(
+      'path' => 'glossary',
+      'title' => t('glossary'),
+      'callback' => 'glossary_page',
+      'access' => user_access('access content'),
+      'type' => MENU_SUGGESTED_ITEM);
+
+    $items[] = array(
+      'path' => 'glossary/',
+      'title' => t('view'),
+      'callback' => 'glossary_page',
+      'access' => user_access('access content'),
+      'type' => MENU_DEFAULT_LOCAL_TASK,
+      'weight' => -10);
+  }
+  return $items;
+}
+
+/**
+ * Display settings for a filter chain
+ */
+function _glossary_filter_settings($format) {
+  $output = form_select(t("Match type"), "glossary_match_$format", variable_get("glossary_match_$format", "match"), array("b" => t("word"), "lr" => t("right or left substring"), "l" => t("left substring"), "r" => t("right substring"), "s" => t("any substring")), t("Choose the match type of glossary links."));
+  $output .= form_select(t("Case sensitivity"), "glossary_case_$format", variable_get("glossary_case_$format", "1"), array(t("case insensitive"), t("case sensitive")), t("Match either case sensitive or not. Case sensitive matches are far not that resource intensive."));
+  $output .= form_select(t("Replace matches"), "glossary_replace_all_$format", variable_get("glossary_replace_all_$format", 0), array(t("only the first match"), t("all matches")), t("Whether only the first match should be replaced or all matches."));
+  $output .= form_select(t("Term Indicator"), "glossary_replace_$format", variable_get("glossary_replace_$format", "superscript"), array("superscript" => t("superscript"), "icon" => t("icon"), "acronym link" => t("replace with acronym link")), t("Display terms using either a superscript formatted link, an icon, or an &lt;acronym&gt; tag."));
+  $output .= form_textfield(t("Superscript"), "glossary_superscript_$format", variable_get("glossary_superscript_$format", "i"), 15, 255, t("If you choose %superscript above, enter the superscript text.",array("%superscript" => "<strong>" . t("superscript") . "</strong>")));
+  $output .= form_textfield(t("Glossary Icon URL"), "glossary_icon_$format", variable_get("glossary_icon_$format", "glossary.gif"), 50, 255, t("If you choose %icon above, enter the URL of the glossary icon relative to the root of your Drupal site.", array("%icon" => "<strong>" . t("icon") . "</strong>")));
+  return form_group(t("Glossary filter"),$output);
+}
+
+/**
+ * Implement hook_filter_tips().
+ */
+function glossary_filter_tips($delta, $format, $long = false) {
+  return $long ? glossary_help('filter#long-tip') : glossary_help('filter#short-tip');
+}
+
+/**
+ * Implement hook_filter().
+ */
+function glossary_filter($op, $delta = 0, $format = -1, $text = "", $node = null) {
+  switch ($op) {
+    case "list":
+      return array(0 => t("Glossary filter"));
+    case "description":
+      return glossary_help('admin/modules#description');
+    case "process":
+      return _glossary_filter_process($text, $format, $node);
+    case "settings":
+      return _glossary_filter_settings($format);
+    default:
+      return $text;
+  }
+}
+
+/**
+ * Process the filter text making sure we don't add glossary links back
+ * to the same term.
+ */
+function _glossary_filter_process($text, $format, $node) {
+
+  $text = " ". $text ." ";
+  $replace_mode = variable_get("glossary_replace_$format", "superscript");
+  $terms = _glossary_get_terms();
+
+  foreach ($terms as $term) {
+    if (!isset($node) || $node->nid != $term->nid) {
+      $term_title = str_replace("\\", "\\\\", drupal_specialchars($term->name. ": ". strip_tags($term->teaser)));
+      $linkto     = ($term->nodes > 0) ? "glossary/$term->nid" : "glossary#term$term->nid";
+      $ins_before = $ins_after = '';
+
+      switch ($replace_mode) {
+        case "superscript":
+          $ins_after = l(variable_get("glossary_superscript_$format", "i"), $linkto, $attribs);
+          break;
+        case "acronym link":
+          $ins_before = '<a class="glossary_term" href="' . url($linkto) . '">';
+          $ins_before .= "<acronym title=\"$term_title\">";
+          $ins_after = '</acronym></a>';
+          break;
+        default:
+          unset($attribs['class']);
+          $img = "<img src=\"". variable_get("glossary_icon_$format", "glossary.gif"). "\" />";
+          $ins_after = l($img, $linkto, $attribs);
+          break;
+      }
+
+      // replace term and synonyms with the desired new HTML code
+      $text = _glossary_insertlink($text, $term->title, $ins_before, $ins_after, $format);
+      foreach ($term->synonyms as $candidate) {
+        $text = _glossary_insertlink($text, $candidate, $ins_before, $ins_after, $format);
+      }
+    }
+  }
+  return $text;
+}
+
+/**
+* Insert glossary links to $text after every $match that is not inside a link.
+* $ins_before is prepended to the matches, $_insafter is appended to them.
+* Match type and replace mode all depend on user settings.
+*
+* TODO: improve performance with not keeping *2.5 copies* of the string in memory:
+*         $text                 - original
+*         $newtext              - transformed
+*         $before . $this_match - for checking stuff
+*
+* TODO: improve partial string matches. currently it will insert the image or link
+*       in the middle of a word instead of the full word on a match.
+*/
+function _glossary_insertlink(&$text, $match, $ins_before, $ins_after, $format) {
+
+  $findfunc = (variable_get("glossary_case_$format", "1") ? "strpos" : "stripos");
+  $next = $findfunc($text, $match);
+
+  if ($next === FALSE) { // no match at all
+    return $text;
+  }
+  else { // at least one match
+    $prevend    = 0;
+    $newtext    = '';
+    $matchlen   = strlen($match);
+    $textlen    = strlen($text);
+    $replaceall = variable_get("glossary_replace_all_$format", 0);
+
+    while ($next && ($next <= $textlen)) {
+
+      // get parts of the match for further investigation
+      $before     = substr($text, 0, $next);
+      $this_match = substr($text, $next, $matchlen);
+
+      // see if we have a proper match or not
+      $open  = substr_count($before, "<");
+      $close = substr_count($before, ">");
+      $opena  = substr_count($before, "<a ");
+      $closea = substr_count($before, "</a>");
+      $openacro  = substr_count($before, "<acronym");
+      $closeacro = substr_count($before, "</acronym>");
+      $proper_match = FALSE;
+      if ($opena <= $closea && $open <= $close && $openacro <= $closeacro) { // Not in an open link
+        switch (variable_get("glossary_match_$format", "b")) {
+          case "lr": // require word break left or right
+              $proper_match = (_glossary_is_boundary($text{$next-1}) ||
+                               _glossary_is_boundary($text{$next+$matchlen}));
+              break;
+          case "b": // require word break left and right
+              $proper_match = (_glossary_is_boundary($text{$next-1}) &&
+                               _glossary_is_boundary($text{$next+$matchlen}));
+              break;
+          case "l":  // require word break left
+              $proper_match = _glossary_is_boundary($text{$next-1});
+              break;
+          case "r": // require word break right
+              $proper_match = _glossary_is_boundary($text{$next+$matchlen});
+              break;
+          case "s": // match any substring
+          default:
+              $proper_match = TRUE;
+              break;
+        }
+      }
+
+      if ($proper_match) { // found match
+        $newtext .= substr($text, $prevend, ($next-$prevend)) . $ins_before . $this_match . $ins_after;
+        if ($replaceall == 0) { return $newtext . substr($text, $next + $matchlen); }
+      }
+      else { // not applicable match
+        $newtext .= substr($text, $prevend, ($next-$prevend)) . $this_match;
+      }
+
+      // Step further in finding the next match
+      $prevend = $next + $matchlen;
+      $next = $findfunc($text, $match, $prevend);
+    }
+    // Append remaining part
+    return $newtext . substr($text, $prevend);
+  }
+}
+
+/**
+ * Menu callback to display glossary
+ *
+ * TODO Add paging since glossary may become large on some sites
+ */
+function glossary_page() {
+  $output = _glossary_alphabar();
+  $terms = _glossary_get_terms();
+  $output .= _glossary_anchor('a', strtolower($terms[0]->title[0]));
+  $output .= '<dl class="glossary">';
+  foreach ($terms as $term) {
+
+    $term = node_prepare($term, true);
+
+    // skip this on first iteration since was already outputted above
+    if (isset($lastletter)) {
+      $firstletter = strtolower($term->title[0]);
+      $output .= _glossary_anchor(chr(ord($lastletter)+1) , $firstletter);
+    }
+    $output .= "<div class=\"glossary-term\"><dt id=\"term{$term->nid}\" class=\"title\">" . l($term->title, "node/$term->nid");
+    $output .= "</dt><dd>{$term->teaser}";
+    if ($links = link_node($term, true)) {
+      $output .= '<div class="links">'. theme('links', $links) .'</div>';
+    }
+    $output .= "</dd></div>";
+    $lastletter = strtolower($term->name[0]);
+  }
+  $output .= '</dl>';
+  $output .= _glossary_anchor(chr(ord($lastletter)+1), 'z');
+
+  print theme("page", $output, t("Glossary"));
+}
+
+/**
+ * Generate the alphabet button bar
+ */
+function _glossary_alphabar() {
+  $output = "\n<div class=\"glossary-links\">";
+  foreach (range('a', 'z') as $letter) {
+    $letters[] = l($letter, 'glossary', NULL, NULL, 'letter' . $letter);
+  }
+  $output .= theme_links($letters);
+  $output .= "</div>\n";
+  return $output;
+}
+
+/**
+ * Generate an anchor tag
+ */
+function _glossary_anchor($lower, $upper) {
+  foreach (range($lower, $upper) as $letter) {
+    $output .= "<a id=\"letter$letter\"></a>\n";
+  }
+  return $output;
+}
+
+/**
+ * Returns a list of all published terms and their synonyms.
+ */
+function _glossary_get_terms() {
+  static $terms = FALSE;
+
+  if ($terms === FALSE) {
+    $terms = array();
+
+    $result = db_query("SELECT n.* FROM node n WHERE n.type = 'glossary' AND n.status = 1 ORDER BY n.title ASC");
+    while ($term = db_fetch_object($result)) {
+      $term->synonyms = array();
+      $terms[$term->nid] = $term;
+    }
+
+    $result = db_query("SELECT gs.nid, gs.synonym FROM {glossary_synonym} gs INNER JOIN {node} n ON gs.nid = n.nid AND n.status = 1");
+    while ($synonym = db_fetch_object($result)) {
+      $terms[$synonym->nid]->synonyms[] = $synonym->synonym;
+    }
+  }
+
+  return $terms;
+}
+
+?>
\ No newline at end of file
Index: glossary.mysql
===================================================================
RCS file: glossary.mysql
diff -N glossary.mysql
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ glossary.mysql	2 Oct 2004 03:23:56 -0000
@@ -0,0 +1,5 @@
+CREATE TABLE glossary_synonym (
+  nid int(10) unsigned NOT NULL default '0',
+  synonym varchar(255) NOT NULL default '',
+  KEY nid (nid)
+) TYPE=MyISAM;
