ActionApps PHP Cross Reference Groupware Applications

Source: /modules/site/sitetree.php3 - 647 lines - 23462 bytes - Summary - Text - Print

   1  <?php
   2  //$Id: sitetree.php3 2358 2007-02-06 12:30:01Z honzam $
   3  /*
   4  Copyright (C) 1999, 2000 Association for Progressive Communications
   5  http://www.apc.org/
   6  
   7      This program is free software; you can redistribute it and/or modify
   8      it under the terms of the GNU General Public License as published by
   9      the Free Software Foundation; either version 2 of the License, or
  10      (at your option) any later version.
  11  
  12      This program is distributed in the hope that it will be useful,
  13      but WITHOUT ANY WARRANTY; without even the implied warranty of
  14      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15      GNU General Public License for more details.
  16  
  17      You should have received a copy of the GNU General Public License
  18      along with this program (LICENSE); if not, write to the Free Software
  19      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20  */
  21  
  22  /** Removes empty positions and normalizes keys to be from 0 to x with step of 1
  23  */
  24  function normalize_arr(&$arr) {
  25      $ret = false;
  26      if ( isset($arr) AND is_array($arr) ) {
  27          foreach ($arr as $v) {
  28              if ( isset($v) ) {
  29                  $ret[] = $v;
  30              }
  31          }
  32      }
  33      return $ret;
  34  }
  35  
  36  
  37  // SiteTree and Spot class definition
  38  $SPOT_VAR_NAMES = array ('id'         => 'id',      // translation from long variable
  39                           'name'       => 'n',       // names to the current - shorter
  40                           'conditions' => 'c',
  41                           'variables'  => 'v',
  42                           'parent'     => 'p',
  43                           'positions'  => 'po',
  44                           'choices'    => 'ch',
  45                           'flag'       => 'f');
  46  
  47  class spot {
  48      var $id;          // spot id
  49      var $n;           // spot name
  50      var $c;           // spot conditions
  51      var $v;           // spot variables
  52      var $p;           // id of parent spot
  53      var $po;          // positions - array of spot ids defining the sequence
  54      var $ch;          // choices - array of spot ids defining the choices for the spot
  55      var $f;           // flags
  56      // the names of variables are short in order the outpot of serialize() function
  57      // would be as short as possible
  58  
  59      function spot($id=false, $name=false, $conditions=false, $variables=false, $parent=false, $positions=false, $choices=false, $flag=0) {
  60          $this->id = $id;
  61          $this->n  = $name;
  62          $this->c  = $conditions; // Array of conditions to match to be this
  63          // branch executed
  64  
  65          $this->v  = $variables;   // Array of variable names used in
  66          // branching. The only spots with variables
  67          // defined may branch the code
  68          $this->p  = $parent;
  69          $this->po = $positions;
  70          $this->ch = $choices;
  71          $this->f  = $flag;
  72      }
  73  
  74      function addInSequence(&$spot) {
  75          $this->po[] = $spot->Id();
  76          $spot->set('parent', $this->Id());  // set/repair parent of inserted spot
  77      }
  78  
  79      function addChoice(&$spot) {
  80          $this->ch[] = $spot->Id();
  81          $spot->set('parent', $this->Id());  // set/repair parent of inserted spot
  82      }
  83  
  84      function addVariable($name) {
  85          $this->v[$name] = $name;
  86      }
  87  
  88      function removeVariable($name) {
  89          unset($this->v[$name]);
  90      }
  91  
  92      function addCondition($var, $cond) {
  93          $this->c[$var] = $cond;
  94      }
  95  
  96      function removeCondition($name) {
  97          unset($this->c[$name]);
  98      }
  99  
 100      function isLeaf() {
 101          return ((!is_array($this->ch) OR (count($this->ch)<1)) AND (count($this->po)<2));
 102      }
 103  
 104      /** Returns true, if the $spot_id is in sequence of this spot (position) */
 105      function isSequence($spot_id) {
 106          return (false !== ($k = array_search($spot_id, (array)$this->po)));
 107      }
 108  
 109      /** Returns true, if the $spot_id is choice of this spot */
 110      function isChoice($spot_id) {
 111          return (false !== ($k = array_search($spot_id, (array)$this->ch)));
 112      }
 113  
 114      /** Check the positions (po) array and choices (ch) array,
 115      *  and fixes possible problems
 116      *     - removes empty positions (where they come from?)
 117      *     - normalizes keys to be from 0 to .. with step of 1
 118      */
 119      function normalize() {
 120          $this->po = normalize_arr($this->po);
 121          $this->ch = normalize_arr($this->ch);
 122      }
 123  
 124      function removeSpot( $spot_id ) {
 125          // search in options
 126          $priorsib = $this->id;
 127          if (isset($this->ch) AND is_array($this->ch)) {
 128              foreach ($this->ch as $k => $v) {
 129                  if ( $v == $spot_id ) {
 130                      unset($this->ch[$k]);
 131                      return $priorsib;   // Returning prior sibling
 132                  }
 133                  $priorsib = $v;  // Used for where to move pointer to
 134              }
 135          }
 136          //search in sequence
 137          if ( isset($this->po) AND is_array($this->po) ) {
 138              foreach ($this->po as $k => $v) {
 139                  if ( $v == $spot_id ) {
 140                      unset($this->po[$k]);
 141                      return $priorsib;   // Returning prior sibling
 142                  }
 143                  $priorsib = $v;  // Used for where to move pointer to
 144              }
 145          }
 146          return false;
 147      }
 148  
 149      function moveUp( $spot_id ) {
 150          $this->normalize();  // just check, if there are no problems in this spot
 151  
 152          // search in options
 153          if (false !== ($k = array_search($spot_id, (array)$this->ch))) {
 154              if ( $k == 0 ) {
 155                  return false;
 156              }
 157              $this->ch[$k]   = $this->ch[$k-1];
 158              $this->ch[$k-1] = $spot_id;
 159              return true;
 160          }
 161          //search in sequence
 162          if (false !== ($k = array_search($spot_id, (array)$this->po))) {
 163              if ( $k<=1 ) {   // can't move to the first position in sequence
 164                  return false;
 165              }
 166              $this->po[$k]   = $this->po[$k-1];
 167              $this->po[$k-1] = $spot_id;
 168              return true;
 169          }
 170          return false;
 171      }
 172  
 173      function moveDown( $spot_id ) {
 174          $this->normalize();  // just check, if there are no problems in this spot
 175  
 176          // search in options
 177          if (false !== ($k = array_search($spot_id, (array)$this->ch))) {
 178              if ($k == count($this->ch)-1) { // last
 179                  return false;
 180              }
 181              $this->ch[$k]   = $this->ch[$k+1];
 182              $this->ch[$k+1] = $spot_id;
 183              return true;
 184          }
 185          //search in sequence
 186          if (false !== ($k = array_search($spot_id, (array)$this->po))) {
 187              if (($k==0) OR ($k==count($this->po)-1)) {    // can't move to the first position in sequence
 188                  return false;
 189              }
 190              $this->po[$k]   = $this->po[$k+1];
 191              $this->po[$k+1] = $spot_id;
 192              return true;
 193          }
 194          return false;
 195      }
 196  
 197      function Name()                        { return $this->n; }
 198      function Id()                          { return $this->id; }
 199      function Conditions()                  { return $this->c; }
 200      function Variables()                   { return $this->v; }
 201  
 202      function get_translated($what)         { return $this->$what; }
 203      function get($what)                    { return $this->get_translated($GLOBALS['SPOT_VAR_NAMES'][$what]); }
 204  
 205      function set_translated($what, $value) { $this->$what = $value; }
 206      function set($what,$value)             { $this->set_translated($GLOBALS['SPOT_VAR_NAMES'][$what], $value); }
 207  
 208  
 209      function conditionMatches(&$state) {
 210          $i=0;
 211          if ( isset($this->c) AND is_array($this->c) ) {  //c is array of conditions
 212              foreach ($this->c as $var => $cond) {
 213                  if (!ereg($cond, $state[$var])) {
 214                      return false;
 215                  }
 216              }
 217          }
 218          return true;
 219      }
 220  };
 221  
 222  class sitetree {
 223      var $tree; // Array of spots
 224      var $start_id;
 225  
 226      function sitetree($spot=false) {
 227          $this->tree[1]  = new spot( $spot['spot_id'], $spot['name'] ? $spot['name']:'start', $spot['conditions'], $spot['variables'], $spot['spot_id'], array($spot['spot_id']), $spot['flag'] );
 228          $this->start_id = $spot['spot_id'];
 229      }
 230  
 231      /** Creates the spot object and adds it in the sequence (positions) */
 232      function addInSequence($where, $name, $content=false, $conditions=false, $variables=false, $flag=false) {
 233          // parent is not set yet (set by addInSequence() in next step);
 234          $spot = new spot( $this->new_id(), $name, $conditions, $variables, false, $flag );
 235          if ($this->_addInSequence($spot, $where)) {
 236              $this->tree[$spot->Id()] = $spot;
 237              return true;
 238          }
 239          return false;
 240      }
 241  
 242      /** Adds already created spot object into sequence (positions array) */
 243      function _addInSequence(&$spot, $where) {
 244          //get real parent
 245          $parent_spot =& $this->tree[$where];
 246  
 247          // this is true for simple spot, which is normal member of any sequence
 248          // real parent must have positions set
 249          if ( !$parent_spot->get('positions') ) {
 250              // if we want to add spot to simple spot in sequence, then we have
 251             // to add it its parent (the first in the sequence
 252              $parent_spot =& $this->tree[$parent_spot->get('parent')];
 253          }
 254  
 255          // parent is set by addInSequence() in next step;
 256          $parent_spot->addInSequence($spot);  // Note this is going to the spot, not recursing
 257  
 258          return true;
 259      }
 260  
 261      function new_id() {
 262          return max(array_keys($this->tree))+1;
 263      }
 264  
 265      /** Creates the spot object and adds it in the choices array */
 266      function addChoice($where, $name, $content=false, $conditions=false, $variables=false, $flag=false) {
 267          // parent is not set yet (set by addChoice() in next step);
 268          $new_id = $this->new_id();
 269          $spot = new spot( $new_id, $name, $conditions, $variables, false, array($new_id), $flag );
 270          if ($this->_addChoice($spot, $where)) {
 271              $this->tree[$spot->Id()] = $spot;
 272              return true;
 273          }
 274          return false;
 275      }
 276  
 277      /** Adds already created spot object in the choices array */
 278      function _addChoice(&$spot, $where) {
 279          //get real parent
 280          $where_spot =& $this->tree[$where];
 281          if (!$where_spot->get('variables')) {  // before creating choice must be defined the list of dependency variables
 282              return false;
 283          }
 284          // parent is set by addChoice() in next step;
 285          $where_spot->addChoice($spot);
 286          return true;
 287      }
 288  
 289      function removeSpot( $spot_id ) {
 290          $spot =& $this->tree[$spot_id];
 291  
 292          if ($spot AND $spot->isLeaf()) {
 293              $parent_id = $spot->get('parent');
 294              $parent =& $this->tree[$parent_id];
 295              if (!$parent) {
 296                  return false;
 297              }
 298              if ( $priorsib = $parent->removeSpot($spot_id)) {
 299                  unset($this->tree[$spot_id]);
 300                  return $priorsib;
 301              }
 302          }
 303          return false;
 304      }
 305  
 306      /** Moves the spot up or down within the sitetree. The move is done only
 307       *  within the same parent.
 308       *  @param $spot_id int - id of spot to be moved
 309       *  @param $direction string - 'moveDown' or 'moveLeft'
 310       */
 311      function move($spot_id, $direction) {
 312          $spot =& $this->tree[$spot_id];
 313          if (!$spot) {
 314              return false;
 315          }
 316          $parent_id = $spot->get('parent');
 317          $parent =& $this->tree[$parent_id];
 318          if (!$parent) {
 319              return false;
 320          }
 321          return $parent->$direction($spot_id);
 322      }
 323  
 324  
 325      /** Moves the spot left (to the parent) or right (to first child) within
 326       *  the sitetree.
 327       *  @param string $direction - 'moveLeft' or 'moveRight'
 328       */
 329      function moveLeftRight($spot_id, $direction) {
 330          $spot =& $this->tree[$spot_id];
 331          if (!$spot) {
 332              return false;
 333          }
 334          $parent_id = $spot->get('parent');
 335          $parent =& $this->tree[$parent_id];
 336          if (!$parent) {
 337              return false;
 338          }
 339  
 340          $spot_type = $parent->isChoice($spot_id) ? 'choice' : 'sequence';
 341  
 342          $destination_parent_id = false;
 343          if ($direction == 'moveLeft') {
 344              // destination_parent - parent of our parent (where we are going to move the spot)
 345              $destination_parent_id = $parent->get('parent');
 346          } else {       // 'moveRight'
 347              // fing next spot in the current spot-set (positions/choices)
 348              $sibling_id = $spot_id;
 349              while (false !== ($sibling_id = $this->getNextSibling($sibling_id))) {
 350                  if ( $this->haveBranches($sibling_id) ) {
 351                      if ($spot_type == 'choice') {
 352                          // if the moved spot is choice, then we just add it to choices
 353                          $destination_parent_id = $sibling_id;
 354                      } else {
 355                          // in case the spot is normal sequence spot, then we
 356                          // have to add it to first option
 357                          $choices = $this->get('choices', $sibling_id);
 358                          $destination_parent_id = ((is_array($choices) AND isset($choices[0])) ? $choices[0] : false);
 359                      }
 360                      break;
 361                  }
 362              }
 363          }
 364  
 365          if (false === $destination_parent_id) {  // destination_parent not found
 366              return false;
 367          }
 368  
 369          $destination_parent =& $this->tree[$destination_parent_id];
 370          if (!$destination_parent) {
 371              return false;
 372          }
 373          $parent->normalize();
 374          $destination_parent->normalize();
 375  
 376          if (!$parent->removeSpot($spot_id)) {
 377              return false;
 378          }
 379  
 380          if ($spot_type == 'choice') {
 381              $this->_addChoice($spot, $destination_parent_id);
 382          } else {
 383              $this->_addInSequence($spot, $destination_parent_id);
 384          }
 385          return true;
 386      }
 387  
 388      /** Returns id of next sibling - the next spot in the set
 389       *  (positions or choices) of the given $spot_id
 390       */
 391      function getNextSibling($spot_id) {
 392          $spot =& $this->tree[$spot_id];
 393          if (!$spot) {
 394              return false;
 395          }
 396          $parent_id = $spot->get('parent');
 397          $parent =& $this->tree[$parent_id];
 398          if (!$parent) {
 399              return false;
 400          }
 401  
 402          // get id of the destination spot for Right movement (tree admin)
 403          $spot_set = $this->isOption($spot_id) ? $parent->get("choices") : $parent->get("positions");
 404          if (!$spot_set) {
 405              echo 'something is wrong - no "positions" or "choices" for parent';
 406              return false;
 407          }
 408          // now we are looking for the spot which is on the same level under
 409          $found = false;
 410          foreach ($spot_set as $poskey => $pos) {
 411              if (!$pos) {     // There was a bug that introduced empty
 412                  continue;    // positions - this is to skip them.
 413              }
 414              if (!$found AND ($pos == $spot_id)) {
 415                  $found = true;
 416                  continue;
 417              }
 418              if ($found) {
 419                  return $pos;
 420              }
 421          }
 422          if (!$found) {
 423              echo 'something is wrong - spot is not found in the spot-set of its parent';
 424          }
 425          return false;
 426      }
 427  
 428      function addVariable($where, $var) {
 429          //get real parent
 430          $where_spot =& $this->tree[$where];
 431          if (!$where_spot) {
 432              return false;
 433          }
 434          $where_spot->addVariable($var);
 435          return true;
 436      }
 437  
 438      function removeVariable($where, $var) {
 439          //get real parent
 440          $where_spot =& $this->tree[$where];
 441          if (!$where_spot) {
 442              return false;
 443          }
 444          $where_spot->removeVariable($var);
 445          return true;
 446      }
 447  
 448      function addCondition( $where, $var, $cond ) {
 449          //get real parent
 450          if (!$this->isChoice($where)) {
 451              return false;
 452          }
 453          $where_spot =& $this->tree[$where];
 454          $where_spot->addCondition($var, $cond);
 455          return true;
 456      }
 457  
 458      function removeCondition( $where, $var ) {
 459          //get real parent
 460          $where_spot =& $this->tree[$where];
 461          if (!$where_spot) {
 462              return false;
 463          }
 464          $where_spot->removeCondition($var);
 465          return true;
 466      }
 467  
 468      function setFlag($spot_id, $flag) {
 469          $current_flag = $this->get('flag', $spot_id);
 470  
 471          // set "structural" flag - stored in structure (not in site_spot table)
 472          $current_flag |= $flag;
 473          $this->set('flag', $spot_id, $current_flag); // wite the state also to the structure
 474      }
 475  
 476      function clearFlag($spot_id, $flag) {
 477          $current_flag = $this->get('flag', $spot_id);
 478  
 479          // set "structural" flag - stored in structure (not in site_spot table)
 480          $current_flag &= ~$flag;
 481          $this->set('flag', $spot_id, $current_flag); // wite the state also to the structure
 482      }
 483  
 484      function isFlag($spot_id, $flag) {
 485          return $this->get('flag', $spot_id) & $flag;
 486      }
 487  
 488      function isChoice($spot_id) {
 489          $spot =& $this->tree[$spot_id];
 490          if (!$spot) {
 491              return false;
 492          }
 493          $parent_spot_id = $spot->get('parent');
 494          if (!$parent_spot_id OR !($vars=$this->get('variables',$parent_spot_id))) {
 495              return false;
 496          }
 497          return $vars;
 498      }
 499  
 500      function isOption($spot_id) {
 501          $spot =& $this->tree[$spot_id];
 502          if (!$spot) {
 503              return false;
 504          }
 505          $parent_spot_id = $spot->get('parent');
 506          if ( !$parent_spot_id OR !($choices=$this->get('choices',$parent_spot_id)) ) {
 507              return false;
 508          }
 509          if (isset($choices) AND is_array($choices)) {
 510              foreach ($choices as $v) {
 511                  if ($v == $spot_id) {
 512                      return $this->get('variables',$parent_spot_id);
 513                  }
 514              }
 515          }
 516          return false;
 517      }
 518  
 519      // Find the spot from the tree, and then do a get on the spot.
 520      function get($what, $id) {
 521          $s =& $this->tree[$id];
 522          return $s ? $s->get($what) : false;
 523      }
 524  
 525      function set($what, $id, $value) {
 526          $s =& $this->tree[$id];
 527          if ($s) {
 528              $s->set($what,$value);
 529          }
 530      }
 531  
 532      function getName($id) { return $this->get( 'name', $id ); }
 533      function exist($id)  { return isset($this->tree[$id]); }
 534  
 535      function haveBranches($id) {
 536          return $this->get('choices', $id) ? true : false;
 537      }
 538  
 539      function isSequenceStart($id) {
 540          return $this->get('positions', $id) ? true : false;
 541      }
 542  
 543      function isLeaf($id)  {
 544          $s =& $this->tree[$id];
 545          return $s->isLeaf();
 546      }
 547  
 548      function conditionMatches( $id, &$state ) {
 549          $s =& $this->tree[$id];
 550          return $s ? $s->conditionMatches($state) : false;
 551      }
 552  
 553      /** Walk the tree, starting at $id, calling $functions for each spot
 554       *
 555       *
 556       */
 557      function walkTree(&$state, $id, $functions, $method='cond', $depth=0) {
 558          global $debugsite;
 559          // $functions could be array which defines the callback functions,
 560          // or nonarray - just main - spot -  function
 561          if (!is_array($functions)) {
 562              $foo['spot'] = $functions;
 563              // functions are array now
 564              $functions = $foo;
 565          }
 566          $function_spot = $functions['spot'];
 567  
 568          if ($debugsite) huhl("state=",$state,"id=",$id,"function=$function method=$method depth=$depth");
 569          $current =& $this->tree[$id];
 570          $positions = $current->get("positions");
 571          if (!$positions) {
 572              echo 'something is wrong - no "positions" for parent';
 573              exit;
 574          }
 575          foreach ($positions as $poskey => $pos) {
 576              if ($debugsite) huhl("Position",$pos);
 577              if ($pos) {
 578                  // There is a bug that introduced empty positions
 579                  // this is to skip them.
 580                  if (($method=='all') OR ($method=='collapsed') OR (($method=='cond') AND !$this->isFlag($pos, MODW_FLAG_DISABLE))) {
 581                      $function_spot($pos, $depth);
 582                  }
 583  
 584                  // if this position is collapsed, then print only first position
 585                  // and skip the others as well as all the choices
 586                  if (($method == 'collapsed') AND $this->isFlag($pos, MODW_FLAG_COLLAPSE)) {  // MODW_FLAG_COLLAPSE - collapsed branches.
 587                      break;
 588                  }
 589  
 590                  if ($this->haveBranches($pos)) {
 591                      $chcurrent =& $this->tree[$pos];
 592                      $choices = $chcurrent->get("choices");
 593                      if ( !$choices ) {
 594                          echo "something is wrong - haveBranches but it has not choices[]";
 595                          exit;
 596                      }
 597                      ksort($choices); // might not be in key order
 598                      $choices_count = count($choices);
 599                      $choices_index = 0;
 600                      foreach ($choices as $k => $cho) {
 601                          if ($debugsite) huhl("Choice: $cho");
 602                          if ($cho) { // skip buggy empty choices
 603                              if (($method=='all') OR ($method=='collapsed') OR ($this->conditionMatches($cho, $state) AND !$this->isFlag($cho, MODW_FLAG_DISABLE))) {
 604  
 605                                  // sometimes it is usefull to call a function before the choice
 606                                  if ($functions['before_choice']) {
 607                                      $functions_before_choice = $functions['before_choice'];
 608                                      $functions_before_choice($cho, $depth, $choices_index, $choices_count);
 609                                  }
 610  
 611                                  $this->walkTree($state, $cho, $functions, $method, $depth+1);
 612  
 613                                  // and sometimes it is usefull to call a function after the choice
 614                                  if ($functions['after_choice']) {
 615                                      $functions_after_choice = $functions['after_choice'];
 616                                      $functions_after_choice($cho, $depth, $choices_index, $choices_count);
 617                                  }
 618                                  $choices_index++;
 619  
 620                                  if ($method=='cond') {
 621                                      break;                 // one matching spot is enough
 622                                  }
 623                              }
 624                          } else {
 625                              if ($GLOBALS["sitefix"]) {
 626                                  huhl("Before fix position($pos)=",$chcurrent);
 627                                  unset($chcurrent->ch[$k]);
 628                                  huhl("After fix position($pos)=",$chcurrent);
 629                              } else {
 630                                  huhe("Warning: skipping Empty choice in position=$pos, run with &amp;sitefix=1 to fix; tree=",$this);
 631                              }
 632                          }
 633                      } // each choice
 634                  } // haveBranches
 635              } else {  // Empty $pos
 636                  if ($GLOBALS["sitefix"]) {
 637                      huhl("Before fix: poskey=$poskey val=",$current->po,"cur=", $current);
 638                      unset($current->po[$poskey]);
 639                      huhl("After fix: ",$current);
 640                  } else {
 641                      huhe("Warning: skipping Empty position in id=$id key $poskey of tree=",$this->tree[$id]);
 642                  }
 643              }
 644          } // each position
 645      } // function
 646  };
 647  ?>

title

Description

title

Description

title

Description

title

title

Body