Index: includes/tree.inc
===================================================================
RCS file: includes/tree.inc
diff -N includes/tree.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/tree.inc	12 Dec 2008 00:49:45 -0000
@@ -0,0 +1,555 @@
+<?php
+/**
+ * @file
+ * The tree system, which controls the tree output from database.
+ * This actually reproduce the algorithm described in
+ * http://arxiv.org/abs/0806.3115.
+ * How the tree is build :
+ * Each element is represented by a rational number (numerator and denominator).
+ * We also store for calculations the next sibling values.
+ * We then have some mathematical functions and properties for building the tree.
+ * Element X.Y.Z would values would calculated with :
+ * X + (1 / ( 1 + 1 / (Y + 1 /( 1 + 1 / Z ).
+ * Or given a parent node 'p', his cTh son values will be :
+ * children_numerator = parent_numerator + c * parent_sibling_numerator
+ * children_denominator = parent_denominator + c * parent_sibling_denominator
+ *
+ * Filtering :
+ * we always have :
+ * (parent_numerator / parent_denominator)
+ *             < (children_numerator / children_denominator)
+ * (children_numerator / children_denominator)
+ *             < (parent_numerator_sibling / parent_denominator_sibling)
+ *
+ * Moving subtrees :
+ * Considering the matrix :
+ *  [ numerator numerator_sibling ]
+ *  [ denominator denominator_sibling ]
+ * If M0 is the matrix before moving
+ * If P0 is the parent matrix before moving
+ * If M1 is the matrix after moving
+ * If P1 is the parent matrix after moving
+ * If n is the child position under P0
+ * If m is the child position under P1
+ * Be  D the Matrix :
+ * [  1         0 ]
+ * [  (m - n )  1 ]
+ * We then have :
+ * M1 = P1 * D * P0^-1 * M0 
+ *
+ * Invert matrix are calculated like this :
+ * [ - denominator_sibling  numerator_sibling ]
+ * [ denominator           - numerator        ]
+ *
+ *
+ * Ancestor finding is made with a kind of euclidian division,
+ * from the top of the tree to the children representing the X.Y.Z algorithm.
+ *
+ */
+
+/**
+ * TEMPORARY /
+ * database schema for testing
+  CREATE TABLE `tree_hierarchy_test` (
+    `tid` int(10) unsigned NOT NULL,
+    `numerator` varchar(30) NOT NULL,
+    `denominator` varchar(30) NOT NULL,
+    `right_sibling_numerator` varchar(30) NOT NULL,
+    `right_sibling_denominator` varchar(30) NOT NULL,
+    `float_value` varchar(30) NOT NULL,
+    `parent` varchar(61) NOT NULL,
+    `vid` int(10) NOT NULL,
+    `depth` int(10) NOT NULL,
+    PRIMARY KEY  (`tid`,`numerator`,`denominator`),
+    KEY `float` (`float_value`),
+    KEY `vid` (`vid`),
+    KEY `parent` (`parent`),
+    KEY `numerator` (`numerator`),
+    KEY `denominator` (`denominator`)
+  );
+
+ *
+ *
+ */
+
+/**
+ * Grab a tree, over and under a term, returns array.
+ * @param $numerator
+ *  Numerator value of the term.
+ * @param $denominator
+ *  Denominator value of the term.
+ * @param $depth
+ *  The depth at witch we search for childrens.
+ * @param $vid
+ *  Vid value of the term.
+ * @param $table
+ *  Database table to use.
+ */
+function tree_tree_get($numerator = 0, $denominator = 0, $depth = 0, $vid, $table = 'tree_hierarchy') {
+  bcscale(21);
+  if ($numerator == 0 && $denominator == 0) {
+    // Top level, get all the tree with depth.
+
+    // We grab all the values of the root tree terms.
+    $query = db_select($table);
+    $query->addField($table, 'right_sibling_numerator');
+    $query->addField($table, 'right_sibling_denominator');
+    $query->addField($table, 'float_value');
+    $query->condition('vid', $vid);
+    $query->condition(db_and()
+            ->condition('denominator', '1')
+            );
+    $query->orderBy('float_value', 'ASC');
+    $result = $query->execute();
+    // We grab all the children for each tree.
+    while ($term = $result->fetchObject()) {
+      $query = db_select($table);
+      $query->fields($table);
+      $query->condition('vid', $vid);
+      $query->condition(db_and()
+              ->condition('float_value', $term->float_value, '>')
+              ->condition('float_value', bcdiv($term->right_sibling_numerator, $term->right_sibling_denominator), '<')
+             );
+      if ($depth > 0) {
+        $query->condition(db_and()
+                ->condition('depth', $depth, '<=')
+                );
+      }
+      // Insert into the return value.
+      // TODO test 
+      $tree[] = $query->execute()->fetchAll();
+    }
+  }
+  else {
+    // all the tree for a numerator, denominator
+    // We grab all the values of the term.
+    $query = db_select($table);
+    $query->fields($table);
+    $query->condition('vid', $vid);
+    $query->condition(db_and()
+            ->condition('numerator', $numerator)
+            ->condition('denominator', $denominator)
+            );
+    $term = $query->execute()->fetchObject();
+    // Calculate all the parent of the term.
+    $ancestor_numerator = 0;
+    $ancestor_denominator = 1;
+    $ancestor_sibling_numerator = 1;
+    $ancestor_sibling_denominator = 0;
+    $division_numerator = $numerator;
+    $division_denominator = $denominator;
+    while ($division_numerator > 0 && $division_denominator > 0) {
+      $div = (int) ($division_numerator / $division_denominator);
+      $mod = $division_numerator % $division_denominator;
+      $ancestor_numerator = $ancestor_numerator + $div * $ancestor_sibling_numerator;
+      $ancestor_denominator = $ancestor_denominator + $div * $ancestor_sibling_denominator;
+      $ancestor_sibling_numerator = $ancestor_numerator + $ancestor_sibling_numerator;
+      $ancestor_sibling_denominator = $ancestor_denominator + $ancestor_sibling_denominator;
+      // Grab the parent.
+      $query = db_select($table);
+      $query->fields($table);
+      $query->condition('vid', $vid);
+      $query->condition(db_and()
+          ->condition('numerator', $ancestor_numerator)
+          ->condition('denominator', $ancestor_denominator)
+          );
+      $tree[] = $query->execute()->fetchObject();
+      $division_numerator = $mod;
+      if ($division_numerator > 0) {
+        $division_denominator = $division_denominator - $mod;
+        if ($division_denominator == 0) {
+          $division_denominator == 1;
+        }
+      }
+    }
+    // Grab his children.
+    $query = db_select($table);
+    $query->fields($table);
+    $query->condition('vid', $vid);
+    $query->condition(db_and()
+        ->condition('float_value', $term->float_value, '>')
+        ->condition('float_value', bcdiv($term->right_sibling_numerator, $term->right_sibling_denominator), '<')
+        );
+    // Limit by depth under the term if provided.
+    if ($depth > 0) {
+      $query->condition(db_and()
+          ->condition('depth', $term->depth + $depth, '<=')
+          );
+    }
+    $result = $query->execute();
+    // Fetch into tree.
+    while ($child = $result->fetchObject()) {
+      $tree[] = $child;
+    }
+  }
+  return $tree;
+
+}
+
+
+/**
+ * Save a term in a tree with his parents as input.
+ *
+ * @param $parents
+ *  Contains the parents to store, each element the numerator,denominator values.
+ *  Also contains the tid and vid of the term.
+ * @param $table
+ *  Database table to use.
+ */
+function tree_term_save(&$parents, $table = 'tree_hierarchy') {
+  if ($parents->parent) {
+    bcscale(21);
+    foreach ($parents->parent as $parent) {
+      // Multiple parent case ordering needs to be done elsewhere. 
+      // (that's maybe not the right way)
+      // We insert at the rightest position in a level.
+
+      // Get all values of parent.
+      $query = db_select($table);
+      $query->fields($table);
+      $query->condition('vid', $parents->vid);
+      $query->condition(db_and()
+          ->condition('numerator', $parent['numerator'])
+          ->condition('denominator', $parent['denominator'])
+          );
+      $parent = $query->execute()->fetchObject();
+      // Get parent's last right son.
+      $query = db_select($table);
+      $query->addField($table, 'right_sibling_numerator');
+      $query->addField($table, 'right_sibling_denominator');
+      $query->condition('vid', $parent->vid);
+      $query->condition(db_and()
+          ->condition('parent', $parent->numerator. '-'.$parent->denominator)
+          );
+      $query->orderBy('float_value', 'DESC');
+      $query->range(0, 1);
+      $last_sibling = $query->execute()->fetchObject();
+      // little math explained at the beginning of the file
+      if (isset($last_sibling->right_sibling_numerator)) {
+        $numerator = $last_sibling->right_sibling_numerator;
+        $denominator = $last_sibling->right_sibling_denominator;
+      }
+      else {
+        $numerator = $parent->numerator + $parent->right_sibling_numerator;
+        $denominator = $parent->denominator + $parent->right_sibling_denominator;
+      }
+      $query = db_select($table);
+      $query->condition('vid', $parent->vid);
+      // We store the parent values for convenience in the children database entry.
+      $query->condition(db_and()
+          ->condition('parent', $parent->numerator. '-'.$parent->denominator)
+          );
+      // Child number if the position under the parent where we will insert
+      $child_number = $query->countQuery()->execute()->fetchField();
+      if ($child_number == 0) {
+        $child_number = 1;
+      }
+      else {
+        $child_number = $child_number + 1;
+      }
+      // Insert term.
+      db_insert($table)
+        ->fields(array(
+              'tid' => $parents->tid,
+              'numerator' => $numerator,
+              'denominator' => $denominator,
+              'right_sibling_numerator' => bcadd($parent->numerator , ($child_number + 1)  * $parent->right_sibling_numerator, 0),
+              'right_sibling_denominator' => bcadd($parent->denominator , ($child_number + 1)  * $parent->right_sibling_denominator, 0),
+              'float_value' => bcdiv($numerator, $denominator),
+              'parent' => $parent->numerator . '-' . $parent->denominator,
+              'vid' => $parent->vid,
+              'depth' => ($parent->depth + 1),
+              ))
+        ->execute();
+    }
+  }
+  else {
+    // Top level insert.
+    // We need in this case the vid in parents.
+    $query = db_select($table);
+    $query->addExpression('MAX(numerator)', 'max');
+    $query->condition('vid', $parents->vid);
+    $query->condition(db_and()
+        ->condition('depth', 0)
+        );
+    $max = $query->execute()->fetchField();
+    // First element will start at numerator value 1
+    if ($max == 0) {
+      $numerator = 1;
+    }
+    else {
+      $numerator = $max +1;
+    }
+    db_insert($table)
+      ->fields(array(
+            'tid' => $parents->tid,
+            'numerator' => $numerator,
+            'denominator' => 1,
+            'right_sibling_numerator' => $numerator + 1,
+            'right_sibling_denominator' => 1,
+            'float_value' => $numerator,
+            'parent' => 0,
+            'vid' => $parents->vid,
+            'depth' => 0,
+            ))
+      ->execute();
+  }
+}
+
+/**
+ * Remove an element of a tree.
+ * @param $numerator
+ *  Numerator value of the term.
+ * @param $denominator
+ *  Denominator value of the term.
+ * @param $tid
+ *  Taxonomy term id, for complete removal purpose.
+ * @param $table
+ *  Database table to use.
+ */
+function tree_term_del($numerator, $denominator, $tid = NULL, $vid, $table = 'tree_hierarchy') {
+  //TODO : remove the term
+}
+
+/**
+ * Move a subtree from under a given term, to under the mth child of another term.
+ * @param $numerator
+ *  Numerator value of the term.
+ * @param $denominator
+ *  Denominator value of the term.
+ * @param $parent_numerator
+ *  Numerator value of parent of the term underneath the subtree will be moved.
+ * @param $parent_denominator
+ *  Denominator value of parent of the term underneath the subtree will be moved.
+ * @param $m
+ *  Child number of the new root. The subtree, will be under this child.
+ * @param $vid
+ *  Vid value of the term.
+ * @param $table
+ *  Database table to use.
+ */
+function tree_subtree_move($numerator, $denominator, $parent_numerator, $parent_denominator, $new_root_child_number, $vid, $table = 'tree_hierarchy') {
+  // We take all the values of the term
+  $query = db_select($table);
+  $query->fields($table);
+  $query->condition('vid', $vid);
+  $query->condition(db_and()
+      ->condition('numerator', $numerator)
+      ->condition('denominator', $denominator)
+      );
+  $sub = $query->execute()->fetchObject();
+  $root_value = explode('-', $sub->parent);
+  // We then select the values of its parent ($old_root).
+  $query = db_select($table);
+  $query->fields($table);
+  $query->condition('vid', $vid);
+  $query->condition(db_and()
+      ->condition('numerator', $root_value[0])
+      ->condition('denominator', $root_value[1])
+      );
+  $old_root = $query->execute()->fetchObject();
+  // We select all the childrens under the given term
+  $children = tree_children_get_all($numerator, $denominator, $vid , $table);
+  // We select the new grand father values ($new_root)
+  $query = db_select($table);
+  $query->fields($table);
+  $query->condition('vid', $vid);
+  $query->condition(db_and()
+      ->condition('numerator', $parent_numerator)
+      ->condition('denominator', $parent_denominator)
+      );
+  $new_root = $query->execute()->fetchObject();
+  // Calculate root child number (n):
+  // We calculate the position of the term under his parent
+  $query = db_select($table);
+  $query->condition('vid', $vid);
+  $query->condition(db_and()
+      ->condition('float_value', $sub->float_value, '<')
+      ->condition('parent', $sub->parent)
+      );
+  $child_number = $query->countQuery()->execute()->fetchField();
+  if ($child_number == 0) {
+    $old_root_child_number = 1;
+  }
+  else {
+    $old_root_child_number = $child_number + 1;
+  }
+  bcscale(21);
+  // Calculate numerator and denominator of the mth child of new_root for updating the parent.
+  // Calculate childrens relocation ( Matrix explained at the beginning of the file ).
+  foreach ($children as $child) {
+    $child->new_numerator1 = bcadd(($new_root->numerator + ($new_root_child_number -$old_root_child_number) * $new_root->right_sibling_numerator) * (- $old_root->right_sibling_denominator)
+                                   , $new_root->right_sibling_numerator * $old_root->denominator, 0);
+    $child->new_denominator1 = bcadd(($new_root->denominator + ($new_root_child_number -$old_root_child_number) * $new_root->right_sibling_denominator) * (- $old_root->right_sibling_denominator)
+                                    , $new_root->right_sibling_denominator * $old_root->denominator, 0);
+    $child->new_sibling_numerator1 = bcadd(($new_root->numerator + ($new_root_child_number -$old_root_child_number) * $new_root->right_sibling_numerator) *  $old_root->right_sibling_numerator
+                                          , $new_root->right_sibling_numerator * (- $old_root->numerator), 0);
+    $child->new_sibling_denominator1 = bcadd(($new_root->denominator + ($new_root_child_number -$old_root_child_number) * $new_root->right_sibling_denominator) *  $old_root->right_sibling_numerator
+                                            , $new_root->right_sibling_denominator * (- $old_root->numerator), 0);
+
+    $child->new_numerator = bcadd($child->new_numerator1 * $child->numerator, $child->new_sibling_numerator1 * $child->denominator, 0);
+    $child->new_denominator = bcadd($child->new_denominator1 * $child->numerator, $child->new_sibling_denominator1 * $child->denominator, 0);
+    $child->new_sibling_numerator = bcadd($child->new_numerator1 * $child->right_sibling_numerator, $child->new_sibling_numerator1 * $child->right_sibling_denominator, 0);
+    $child->new_sibling_denominator = bcadd($child->new_denominator1 * $child->right_sibling_numerator, $child->new_sibling_denominator1 * $child->right_sibling_denominator, 0);
+    // We need to get the parent division_numerator and denominator values for the update
+    // Maybe some optimization is needed here.
+    $ancestor_numerator = 0;
+    $ancestor_denominator = 1;
+    $ancestor_sibling_numerator = 1;
+    $ancestor_sibling_denominator = 0;
+    $division_numerator = $child->new_numerator;
+    $division_denominator = $child->new_denominator;
+    while ($division_numerator > 0 && $division_denominator > 0) {
+      $div = (int)($division_numerator / $division_denominator);
+      $mod = $division_numerator % $division_denominator;
+      $parent_ancestor_numerator = $ancestor_numerator;
+      $parent_ancestor_denominator = $ancestor_denominator;
+      $ancestor_numerator = bcadd($ancestor_numerator, $div * $ancestor_sibling_numerator, 0);
+      $ancestor_denominator = bcadd($ancestor_denominator, $div * $ancestor_sibling_denominator, 0);
+      $ancestor_sibling_numerator = bcadd($ancestor_numerator, $ancestor_sibling_numerator, 0);
+      $ancestor_sibling_denominator = bcadd($ancestor_denominator, $ancestor_sibling_denominator, 0);
+      $division_numerator = $mod;
+      if ($division_numerator>0) {
+        $division_denominator = bcsub($division_denominator , $mod, 0);
+        if ($division_denominator == 0) {
+          $division_denominator == 1;
+        }
+      }
+    }
+    // Final update of the term's children
+    db_update($table)
+      ->fields(array(
+        'numerator' => $child->new_numerator,
+        'denominator' => $child->new_denominator,
+        'right_sibling_numerator' => $child->new_sibling_numerator,
+        'right_sibling_denominator' => $child->new_sibling_denominator,
+        'depth' => $new_root->depth + ($child->depth - $old_root->depth),
+        'float_value' => bcdiv($child->new_numerator, $child->new_denominator),
+        'parent' => $parent_ancestor_numerator . '-' . $parent_ancestor_denominator,
+      ))
+      ->condition('numerator', $child->numerator)
+      ->condition(db_and()
+        ->condition('denominator', $child->denominator)
+        ->condition('vid', $vid)
+      )->execute();
+  }
+}
+
+/**
+ * Get the parents of a term.
+ * @param $numerator
+ *  Numerator value of the term.
+ * @param $denominator
+ *  Denominator value of the term.
+ * @param $vid
+ *  Vid value of the term.
+ * @param $table
+ *  Database table to use.
+ */
+
+function tree_parents_get($numerator, $denominator, $vid, $table = 'tree_hierarchy') {
+  // First take numerator / denominator of the float_value.
+  // Calculate all the parent of the term.
+  $ancestor_numerator = 0;
+  $ancestor_denominator = 1;
+  $ancestor_sibling_numerator = 1;
+  $ancestor_sibling_denominator = 0;
+  $division_numerator = $numerator;
+  $division_denominator = $denominator;
+  $parents = array();
+  while ($division_numerator > 0 && $division_denominator > 0) {
+    $div = (int)($division_numerator / $division_denominator);
+    $mod = $division_numerator % $division_denominator;
+    $ancestor_numerator = $ancestor_numerator + $div * $ancestor_sibling_numerator;
+    $ancestor_denominator = $ancestor_denominator + $div * $ancestor_sibling_denominator;
+    $ancestor_sibling_numerator = $ancestor_numerator + $ancestor_sibling_numerator;
+    $ancestor_sibling_denominator = $ancestor_denominator + $ancestor_sibling_denominator;
+    // Grab the parent from database.
+    $query = db_select($table);
+    $query->fields($table);
+    $query->condition('vid', $vid);
+    $query->condition(db_and()
+        ->condition('numerator', $ancestor_numerator)
+        ->condition('denominator', $ancestor_denominator)
+        );
+    $parents[] = $query->execute()->fetchObject();
+    $division_numerator = $mod;
+    if ($division_numerator>0) {
+      $division_denominator = $division_denominator - $mod;
+      if ($division_denominator == 0) {
+        $division_denominator == 1;
+      }
+    }
+  }
+  return $parents;
+}
+
+/**
+ * Get the first level childrens of a term.
+ * @param $numerator
+ *  Numerator value of the term.
+ * @param $denominator
+ *  Denominator value of the term.
+ * @param $vid
+ *  Vid value of the term.
+ * @param $table
+ *  Database table to use.
+ */
+
+function tree_children_get($numerator, $denominator, $vid , $table = 'tree_hierarchy') {
+  // First find numerator and denominator for the first child.
+  if ($denominator == 1) {
+    $parent = 0;
+  }
+  else {
+    $parent = $numerator . '-' . $denominator;
+  }
+  $query = db_select($table);
+  $query->fields($table);
+  $query->condition('vid', $vid);
+  $query->condition(db_and()
+      ->condition('parent', $parent)
+      );
+  $query->orderBy('numerator', 'ASC');
+  $children = $query->execute()->fetchAll();
+  return $children;
+
+}
+
+/**
+ * Get the all level childrens of a term.
+ * @param $numerator
+ *  Numerator value of the term.
+ * @param $denominator
+ *  Denominator value of the term.
+ * @param $vid
+ *  Vid value of the term.
+ * @param $table
+ *  Database table to use.
+ */
+
+function tree_children_get_all($numerator, $denominator, $vid , $table = 'tree_hierarchy') {
+  bcscale(21);
+  // Select all the values of the term.
+  $query = db_select($table);
+  $query->fields($table);
+  $query->condition('vid', $vid);
+  $query->condition(db_and()
+      ->condition('numerator', $numerator)
+      ->condition('denominator', $denominator)
+      );
+  $term = $query->execute()->fetchObject();
+  // Use inequality to select the childrens.
+  $query = db_select($table);
+  $query->fields($table);
+  $query->condition('vid', $vid);
+  $query->condition(db_and()
+      ->condition('float_value', $term->float_value, '>')
+      ->condition('float_value', bcdiv($term->right_sibling_numerator, $term->right_sibling_denominator), '<')
+      );
+  // Fetch childrens.
+  $children = $query->execute()->fetchAll();
+  return $children;
+
+}
+?>
