--- old/pathauto/pathauto.module	2005-02-21 23:00:17.000000000 +0200
+++ pathauto.module	2005-02-28 13:56:07.000000000 +0200
@@ -4,7 +4,7 @@
 function pathauto_help($section = 'admin/modules#description') {
   switch ($section) {
     case 'admin/modules#description':
-      $output = t('Automatically creates aliases from node titles');
+      $output = t('Automatically creates aliases from node properties');
       break;
   }
 
@@ -19,11 +19,8 @@
     switch ($op) {
       case 'insert':
       case 'update':
-        /*
-        ** If there's no explicit alias and there is a title, use the
-        ** title to automatically create an alias
-        */
-        _pathauto_generate_alias($node);
+        if (variable_get('pathauto_enabled',false))
+            _pathauto_generate_alias($node, true);
         break;
       case 'form pre':
         /*
@@ -32,10 +29,24 @@
         ** almost certainly appear immediately after the path alias field 
         */
         if (user_access('create url aliases')) {
-          $output = t('If the <b>Path alias</b> is left blank, an alias '.
-            'will be automatically generated from the title.');
+
+          if (variable_get('pathauto_onlymissing',true))
+            $output = t('If the <b>Path alias</b> is left blank, one or more aliases '.
+                'may be automatically generated based on the content.');
+          else
+            $output = t('Additional path aliases may be automatically generated based on the content.');
+
+          $new_aliases = _pathauto_generate_alias($node, false);
+          if ( count($new_aliases)) {
+            $output .= ' ' . t('For current node, they would be:'). '<ul>';
+            foreach( $new_aliases as $a )
+                $output .= '<li><em>' . htmlspecialchars($a) . '</em></li>';
+            $output .= '</ul>';
+          }
+          else $output .= ' ' . t('Currently for this node, no new aliases would be generated.');
+
           if (user_access('administer pathauto')) {
-            $output .= t(' To control the format of the generated alias, '.
+            $output .= ' ' . t('To control the format of the generated alias(es), '.
               'see the <a href="admin/settings/pathauto">pathauto settings</a>.');
           }
           return $output;
@@ -67,10 +78,61 @@
   
   // Did they ask for a bulk update?
   if (variable_get('pathauto_bulkupdate', FALSE)) {
-    _pathauto_bulkupdate();
+    _pathauto_processall(TRUE);
+  }
+
+  // Test run?
+  if (variable_get('pathauto_testrules', FALSE)) {
+
+    $test_res = _pathauto_processall(FALSE);
+    $rows = array();
+    while ( list($k,$v) = each($test_res))
+    {
+        $als = array();
+        foreach( $v as $a )
+            $als[] = htmlspecialchars($a);
+
+        // Present existing aliases
+        $cnt_aliases = 0;
+        $oa_query = db_query("SELECT dst FROM {url_alias} WHERE src = 'node/$k'");
+        $oa = db_fetch_object($oa_query);
+        while ($oa)
+        {
+            $als[] = '<em style="font-size: 80%;">' . htmlspecialchars("($oa->dst)") . '</em>';
+            $oa = db_fetch_object($oa_query);
+            ++$cnt_aliases;
+        }
+
+        $nfo = db_fetch_object(db_query("SELECT title, type FROM {node} WHERE nid=%d", $k));
+
+        $rows[] = array(
+            "<a href='/node/$k'>$k</a>", $cnt_aliases?'':'*', t($nfo->type),
+            join("<br />", $als), htmlspecialchars($nfo->title));
+    }
+
+    $descr = '<strong>Legend</strong>:<ul>'.
+             '<li>@ = ' . t('Node number, click to view the node') . '</li>' .
+             '<li>? = ' . t('No existing aliases?') . '</li>' .
+             '<li>'. t('Type') .' = ' . t('Node type') . '</li>' .
+             '<li>'. t('Aliases') .' = ' . t('Aliases the rules would generate. Existing aliases are shown in parens.') . '</li>' .
+             '<li>'. t('Title') .' = ' . t('Title of the node') . '</li></ul></small>';
+    $output .= form_group(t('Test results'),
+        theme_table(array('@', '?', t('Type'), t('Aliases'), t('Title')), $rows) . $descr);
   }
 
   // Generate the form
+  $items .= form_checkbox(t('Enabled'), 'pathauto_enabled', 1,
+        variable_get('pathauto_enabled',false),
+        t('Activate/disable automatic aliasing of new nodes. It is a good idea to keep this '.
+          'unchecked while trying out different naming schemes.'));
+
+  $items .= form_checkbox(t('Add only missing'), 'pathauto_onlymissing', 1,
+        variable_get('pathauto_onlymissing',true),
+        t('Add aliases only if there are no aliases already? Note that you could do this check manually in PHP even if you answer \'no\' here.'));
+
+  $output .= form_group(t('General settings'), $items );
+  $items = '';
+
   // %%% NOTE: should validate separator as legal URI character
   $items .= form_textfield(t('Separator'), 'pathauto_separator',  
     variable_get('pathauto_separator', '_'), 1, 1,
@@ -91,7 +153,7 @@
         $description .= '<dd>'.t('As category, but including its supercategories.').'</dd>';
     }
   }
-  $description .= '<dt>'.'[yyyy]'.'</dt>';
+  $description .= '<dt>'.'[yyyy] and [yy]'.'</dt>';
   $description .= '<dd>'.t('The year the node was created.</dd>');
   $description .= '<dt>'.'[mm]'.'</dt>';
   $description .= '<dd>'.t('The two-digit month (01-12) the node was created.').'</dd>';
@@ -110,7 +172,7 @@
   $description .= '<dt>'.'[week]'.'</dt>';
   $description .= '<dd>'.t('The week number (1-52) of the year the node was created.').'</dd>';                        
   $description .= '</dl>';
-  $items .= form_textfield('Pattern for the path',
+  $items .= form_textfield(t('Pattern for the path'),
                            'pathauto_pattern',
                            variable_get('pathauto_pattern','[title]'),
                            65,
@@ -119,27 +181,68 @@
                            NULL,
                            FALSE);
 
-  $examples = t('<b>Examples:</b>'.
+  $script_description = t( <<<EOD
+<p>If the simple pattern language is too restricted for your needs,
+you can write here a PHP script that adds alias strings to an initially empty
+<strong>\$res</strong> array variable. <em>If this field is non-empty, the pattern above is ignored and the separator
+is only used for appending numbers when trying to avoid colliding names.</em></p>
+
+<p>The script can use Drupal's fully populated <strong>\$node</strong> object or
+the pattern placeholders, which are passed as ordinary PHP variables
+(e.g. <strong>[week]</strong> becomes <strong>\$week</strong>).
+Additionally, it is provided with two slightly different views to full categorization,
+<strong>\$taxotree</strong> and <strong>\$taxopaths</strong>. Both are arrays with vocabulary
+names as first level keys. Under them, \$taxopaths contains a string array of topic paths (e.g.
+<strong>strtolower(\$taxopaths['focus'][0])</strong> might return 'sports/ski')
+whereas \$taxotree has a multi level tree of term names plus key '1' for each selected term (example:
+<strong>isset(\$taxotree['focus']['sports']['ski'][1])</strong> is True if term 'sport/ski' of vocabulary
+'focus' is chosen). Function <strong>clean_alias(\$str, \$sep='-')</strong> removes accents and replaces
+all non-ASCII characters with \$sep, making \$str suitable for use in a URL.</p>
+EOD
+);
+
+  $items .= form_textarea(t('PHP script (optional)'),
+                           'pathauto_phpscript',
+                           variable_get('pathauto_phpscript',''),
+                           65,
+                           14,
+                           $script_description,
+                           NULL,
+                           FALSE);
+
+  $examples = t('<b>Pattern examples:</b>'.
         '<p>The following examples of aliases generated from various patterns are all based on a node titled '.
         '"This is a (sample) node", created at 11:23 (am) on February 6, 2005, '.
         'with a category of Sports, which is a subcategory of News, in the '.
         'vocabulary Topics.</p>'.
         '<dl><dt>Separator=<b>_</b>, Pattern=<b>[title]</b></dt>'.
         '   <dd>this_is_a_sample_node</dd>'.
-        '<dl><dt>Separator=<b>-</b>, Pattern=<b>content/[vocab]/[catpath]/[title]</b></dt>'.
+        '<dt>Separator=<b>-</b>, Pattern=<b>content/[vocab]/[catpath]/[title]</b></dt>'.
         '   <dd>content/topics/news/sports/this-is-a-sample-node</dd>'.
-        '<dl><dt>Separator=<b>/</b>, Pattern=<b>[cat]/[yyyy]/[mm]/[dd]/[title]</b></dt>'.
+        '<dt>Separator=<b>/</b>, Pattern=<b>[cat]/[yyyy]/[mm]/[dd]/[title]</b></dt>'.
         '   <dd>sports/2005/02/06/this/is/a/sample/node</dd>'.
-        '</dl>'
+        '</dl>' .
+        '<strong>PHP script example:</strong><p>Use title by default but also add '.
+        'a news-item alias if the node is sports related.</p>' .
+        '<pre>if ( $taxotree["topic"]["sports"] ) '."\n".
+        '  $res[] = "sportsworld/$yyyy/$week/$dd_$hour_$min";' ."\n" .
+        '$res[] = "articles/$title";</pre>'
       );
 
   $output .= form_group(t('Format settings'), $items, $examples);
 
   // %%% Should only show when there are unaliased nodes
   $items = '';
+  $items .= form_checkbox(t('Test the rules'), 'pathauto_testrules', 1, FALSE,
+    t('Test the rules against all existing nodes. This only shows a list of generated aliases '.
+      'without actually adding any.'));
+  variable_set('pathauto_testrules', FALSE);
+
   $items .= form_checkbox(t('Bulk update'), 'pathauto_bulkupdate', 1, FALSE, 
-    t('Generate aliases for all existing nodes which do not already have aliases.'));
+    t('Generate aliases for all existing nodes which do not already have aliases. '.
+      'This works even if \'Enabled\' option is not checked.'));
   variable_set('pathauto_bulkupdate', FALSE);
+
   $output .= form_group(t('Actions'), $items, t(''));
   
   return $output;
@@ -152,9 +255,12 @@
     $alias, $src));
 }
 
-// Generate an alias from a node's title
-function _pathauto_generate_alias($node) {
-  if ($node->title && !$node->path) {
+// Generate an alias from a node's title. If $do_add is true,
+// also add it to the node.
+function _pathauto_generate_alias($node, $do_add) {
+  $alias = false;
+  if (!$node->path || !variable_get('pathauto_onlymissing',true)) {
+
     $separator = variable_get('pathauto_separator', '_');
     
     // First transform the title
@@ -162,61 +268,129 @@
     
     // Now get all the placeholder data
     $placeholders = array();
-    $placeholders['[title]'] = $title;
+    $placeholders['title'] = $title;
 
     // Date & time info
-    $placeholders['[yyyy]'] = date('Y', $node->created);
-    $placeholders['[mm]'] = date('m', $node->created);
-    $placeholders['[mon]'] = strtolower(date('M', $node->created));
-    $placeholders['[dd]'] = date('d', $node->created);
-    $placeholders['[day]'] = strtolower(date('D', $node->created));
-    $placeholders['[hour]'] = date('H', $node->created);
-    $placeholders['[min]'] = date('i', $node->created);
-    $placeholders['[sec]']  = date('s', $node->created);
-    $placeholders['[week]'] = date('W', $node->created);
+    $placeholders['yyyy'] = date('Y', $node->created);
+    $placeholders['yy'] = date('y', $node->created);
+    $placeholders['mm'] = date('m', $node->created);
+    $placeholders['mon'] = strtolower(date('M', $node->created));
+    $placeholders['dd'] = date('d', $node->created);
+    $placeholders['day'] = strtolower(date('D', $node->created));
+    $placeholders['hour'] = date('H', $node->created);
+    $placeholders['min'] = date('i', $node->created);
+    $placeholders['sec']  = date('s', $node->created);
+    $placeholders['week'] = date('W', $node->created);
 
     // Category info
     if (module_exist('taxonomy')) {
       $firsttermid = $node->taxonomy[0];
       $term = taxonomy_get_term($firsttermid);
-      $placeholders['[cat]'] = _pathauto_cleanstring($term->name, $separator);
+      $placeholders['cat'] = _pathauto_cleanstring($term->name, $separator);
       
       $catpath = '';
       $parents = taxonomy_get_parents_all($firsttermid);
       foreach ($parents as $parent) {
         $catpath = _pathauto_cleanstring($parent->name, $separator) . '/' . $catpath;
       }
-      $placeholders['[catpath]'] = $catpath;
+      $placeholders['catpath'] = $catpath;
       
       $vid = $term->vid;
       $vocabulary = taxonomy_get_vocabulary($vid);
-      $placeholders['[vocab]'] = _pathauto_cleanstring($vocabulary->name, $separator);
+      $placeholders['vocab'] = _pathauto_cleanstring($vocabulary->name, $separator);
+
+      // Build a taxonomy tree and path string array.
+      $taxotree = array();
+      $taxopaths = array();
+      $taxon = $node->taxonomy;
+      if ( !is_array($taxon))
+        $taxon = array();
+      foreach ($taxon as $tid)
+      {
+        $term = taxonomy_get_term($tid);
+        $voc = taxonomy_get_vocabulary($term->vid);
+        if ( !isset($taxotree[$voc->name]))
+            $taxotree[$voc->name] = array();
+        $parents = taxonomy_get_parents_all($tid);
+        $branch = &$taxotree[$voc->name];
+        $branch_names = array();
+        foreach (array_reverse($parents) as $p) {
+            $branch_names[] = $p->name;
+            if ( !isset($branch[$p->name]))
+                $branch[$p->name] = array();
+            if ( $p->tid == $tid )
+            {
+                $branch[$p->name][1] = 1;
+                $taxopaths[$voc->name][] = join('/', $branch_names);
+            }
+            $branch = &$branch[$p->name];
+        }
+      }
     }
     
+    $aliases = array();
+    $script = variable_get('pathauto_phpscript','');
+    if (strlen(trim($script))) {
+
+        // Load the node in full
+        $node = node_load( array('nid' => (int)$node->nid ));
+
+        // Provide the script with string sanitizer
+        if ( !function_exists('clean_alias')) {
+            function clean_alias($str, $sep="-") {
+                return _pathauto_cleanstring($str, $sep);
+            }
+        }
+        $res = array();
+        extract($placeholders);
+        eval($script);
+        if ( !is_array($res)) // recover if user e.g. replaced $res with a string
+            $res = array( ''.$res );
+        $aliases = $res;
+    }
+    else {
     // Retrieve and apply the pattern
     $pattern = strtolower(variable_get('pathauto_pattern', '[title]'));
-    $alias = str_replace(array_keys($placeholders), $placeholders, $pattern);
+        $tokens = array();
+        foreach( array_keys($placeholders) as $k )
+            $tokens[] = "[$k]";
+        $alias = str_replace($tokens, $placeholders, $pattern);
     $alias = strtolower($alias);
     // Two or more slashes should be collapsed into one
     $alias = preg_replace("/\/+/", "/", $alias);
     // Trim any leading or trailing slashes
     $alias = preg_replace("/^\/|\/+$/", "", $alias);
+        $aliases[] = $alias;
+    }
 
     $src = "node/$node->nid";
     // If the alias already exists, generate a new variant
-    if (_pathauto_alias_exists($alias, $src)) {
-      for ($i=0; _pathauto_alias_exists($alias.$separator.$i, $src); $i++) {
+    foreach( $aliases as $k => $a ) {
+        if (_pathauto_alias_exists($a, $src)) {
+            for ($i=0; _pathauto_alias_exists($a.$separator.$i, $src); $i++) {
       }
-      $alias = $alias.$separator.$i;
+            $aliases[$k] = $a.$separator.$i;
     }
-    path_set_alias($src, $alias);
   }
-  return $alias;
+    if ($do_add)
+        foreach( $aliases as $a )
+            path_set_alias($src, $a);
+  }
+  return $aliases;
 }
 
+
 function _pathauto_cleanstring($string, $separator) {
   // Drop apostrophes
   $output = str_replace("'", "", $string);
+  // Convert accented characters to their ASCII counterparts
+  $output = utf8_encode(strtr(utf8_decode($output),
+  "\xe1\xc1\xe0\xc0\xe2\xc2\xe4\xc4\xe3\xc3\xe5\xc5".
+  "\xaa\xe7\xc7\xe9\xc9\xe8\xc8\xea\xca\xeb\xcb\xed".
+  "\xcd\xec\xcc\xee\xce\xef\xcf\xf1\xd1\xf3\xd3\xf2".
+  "\xd2\xf4\xd4\xf6\xd6\xf5\xd5\x8\xd8\xba\xf0\xfa".
+  "\xda\xf9\xd9\xfb\xdb\xfc\xdc\xfd\xdd\xff\xe6\xc6\xdf",
+   'aAaAaAaAaAaAacCeEeEeEeEiIiIiIiInNoOoOoOoOoOoOoouUuUuUuUyYyaAs'));
   // Any sequence of one or more non-alphanumerics
   // should become a separator
   $output = preg_replace('/[\W]+/ ', $separator, $output);
@@ -231,11 +405,16 @@
   return $output;
 }
 
-// Generate aliases for all nodes without aliases
-function _pathauto_bulkupdate() {
-  $query = 'SELECT nid,title,created,src,dst FROM {node} '.
+// Generate aliases for all nodes. If $only_listing is false, add them to nodes
+// and if true, only return a list.
+function _pathauto_processall( $do_add ) {
+  if ( variable_get('pathauto_onlymissing', true))
+    $query .= 'SELECT nid,title,created,src,dst FROM {node}' .
     "LEFT JOIN {url_alias} ON CONCAT('node/', nid) = src ".
     'WHERE dst is null';
+  else
+    $query .= 'SELECT nid,title,created FROM {node}';
+
   $result = db_query($query);
   $node = db_fetch_object($result);
   if (!$node) {
@@ -243,15 +422,17 @@
     return;
   }
 
+  $generated = array();
   while ($node) {
     if (module_exist('taxonomy')) {
       // Must populate the terms for the node here for the 
       // category placeholders to work
       $node->taxonomy = array_keys(taxonomy_node_get_terms($node->nid));
     }
-    _pathauto_generate_alias($node);
+    $generated[$node->nid] = _pathauto_generate_alias($node, $do_add);
     $node = db_fetch_object($result);
   }
+  return $generated;
 }
 
 ?>
