Subversion Repositories php_library

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 rodolico 1
<?php
2
 
3
if ( isset($TESTING) ) {
4
   mysql_connect("localhost", "test", "test") or die(mysql_error());
5
   mysql_select_db("camp") or die(mysql_error());
6
   ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . '/home/rodolico/Desktop/wip/common-cgi' );
7
}
8
include_once("library.php");
9
 
10
/* 
11
   class reads a database table and converts it to an Hierarchical hash (aka, parent/child).
12
   Table structure is assume to be:
13
      create table menu (
14
         id        int unsigned not null,
15
         parent_id int unsigned not null default 0,
16
         other columns below
17
      )
18
   where id is a unique identifier and parent_id is points to id in another row (zero indicating it is a top level)
19
   NOTE: column names may be changed. Also, while it is assumed id and parent_id are int's, it is quite likely it will
20
         work with other column types, including varchar
21
   Uses include any parent/child relationship, to an arbitrary depth. See class DBMenu for one example
22
 
23
   This class was written to be generic. The algorithm used attempts to not take advantage of any special language 
24
   capabilities, and should be generic. However, various languages can optimize this capability by using capabilities unique
25
   to the language in question. For an example, see David North's PERL implementation which utilizes references to allow for
26
   a single pass to convert from an array to a Hierarchical structure. Taking advantage of this allows his script to run
27
   use no additional memory (this algorithm will double the amount of memory used for the initial array) and does in a single
28
   iterative pass what the recursive function copyChildren does.
29
*/
30
 
31
class DBHierarchicalHash {
32
   protected $tableName; // table name that data will be read from
33
   protected $keyField;  // name of the primary id field in table
34
   protected $parentFieldName; // name of the field used to point to the parent of the current row
35
   protected $rootNodevalue; // the value (default 0) of the indicator that a row has no parent. THIS MUST NOT EXIST in the id column of the table
36
   protected $inputRecords; // storage for the records read in.
37
 
38
   public function __construct( $tableName, $keyField = 'id', $parentFieldName = 'parent_id', $rootNodeValue = 0 ) {
39
      // standard variable load
40
      $this->tableName = $tableName;
41
      $this->keyField = $keyField;
42
      $this->parentFieldName = $parentFieldName;
43
      $this->rootNodevalue = $rootNodeValue;
44
      // load the data from the database into memory
45
      // upon completion, will have data in a single hash of hashes (not hiearchy)
46
      // will also create an emtpy entry for "element 0"
47
      $this->loadData();
48
      // find the children of each node
49
      $this->findChildren();
50
      // now that we know who all the children are, let's actually make them part of the structure
51
      $this->inputRecords = $this->copyChildren($this->inputRecords[$this->rootNodevalue]);
52
      // all we care about are the children of the root node, so simply assign that to inputRecords
53
      $this->inputRecords = $this->inputRecords['children'];
54
   } // function __construct
55
 
56
 
57
   /*
58
      Goes through each node and reverses the "who is my parent" to the parent's "who are my children"
59
      It does not populate the children; it simply creates an empty array for each child to be processed
60
      later.
61
   */
62
   private function findChildren () {
63
      // following loop takes inputRecords and creates (empty) elements for each child
64
      foreach ($this->inputRecords as $key => $values) { // go through every node
65
         if ( isset ($this->inputRecords[$key][$this->parentFieldName]) ) { // if it has a parent
66
            // tell parent about itself
67
            $this->inputRecords[$values[$this->parentFieldName]]['children'][$key] = array();
68
         }
69
      }
70
   } // function findChildren
71
 
72
   /* 
73
      simply returns inputRecords for processing by calling routine. This is the goal of this class, to create
74
      this structure from the database structure defined above
75
   */
76
   public function toHash () {
77
      return $this->inputRecords;
78
   } // function toHash
79
 
80
   /* 
81
      loads data from database. The column defined in $this->keyField will become the index into the hash, and all
82
      additional columns will simply be added to the array pointed by it.
83
      An additional entry in the table will be created with $this->rootNodeValue as its index, to allow root level
84
      items to have a parent.
85
   */
86
   private function loadData ( ) {
87
      $inputRows = queryDatabaseExtended( "select * from $this->tableName" ); // get everything from the table
88
      $inputRows = $inputRows['data']; // we only care about the data
89
      $this->inputRecords = array(); // initialize inputRecords to empty
90
      // main loop, will read the query results in one row at a time
91
      foreach ( $inputRows as $thisRow ) {
92
         foreach ($thisRow as $key => $values) { // copy each value
93
            if ($key != $this->keyField) { // unless it is the key field
94
               $this->inputRecords[$thisRow[$this->keyField]][$key] = $thisRow[$key];
95
            }
96
         }
97
      }
98
      $this->inputRecords[$this->rootNodevalue] = array(); // create our empty root node
99
   } // function loadData
100
 
101
   # populates the hiearchy created in load
102
   # NOTE: if the current node also has children, it will recursively do those
103
   # main call is &copyChildren( rootNode )
104
   private function copyChildren($currentNode) {
105
      # for each child
106
      //print "Looking at: "; print_r($currentNode);
107
      if ( isset($currentNode['children']) ) {
108
         foreach ($currentNode['children'] as $key => $values) {
109
            # copy any children of this child table that may exist
110
            $this->inputRecords[$key] = $this->copyChildren( $this->inputRecords[$key] );
111
            # copy each value from the node to this sub-node
112
            foreach ($this->inputRecords[$key] as $child => $childValue) {
113
               if ($child != $this->parentFieldName ) {
114
                  $currentNode['children'][$key][$child] = $childValue;
115
               }
116
            }
117
         }
118
      }
119
      return $currentNode;
120
   } // function copyChildren
121
} // class DBHierarchicalHash
122
 
123
 
124
/*
125
   simple extension of DBHierarchicalHash allowing a menu structure to be stored in a database.
126
   the structure for the database is simple:
127
   create table menu (
128
      menu_id     int unsigned not null auto_increment,
129
      parent_id   int unsigned not null default 0,
130
      caption     varchar(20) not null,
131
      url         varchar(64),
132
      primary key (menu_id)
133
   )
134
   where caption is the string displayed on the menu, and url is the (possibly null) url
135
   to be called when the link is clicked by the user. The resulting line is something like:
136
      <a href="url">caption</a>
137
   though this can be modified by changing $menuItemString and $menuHeaderString (see below)
138
 
139
   menu_id is a unique identifier for a single row, and parent_id points to the menu that it
140
   is a submenu of (0 indicates a root menu item)
141
 
142
   Note: I tried to avoid any constants in the class, so column names, menu_id data type, root
143
         menu indicator are all modifiable
144
*/
145
class DBMenu extends DBHierarchicalHash {
146
 
147
   protected  $captionName = 'caption'; // column name for display string in database
148
   protected  $urlName = 'url';         // column name for URL field in database
149
   // string which is searched/replaced for a menu item that has a URL (<url> and <caption> are replaced)
150
   protected  $menuItemString = '<li class="menu_item_<level>"><a href="<url>"><caption></a></li>';
151
   // string which is searched/replaced for a menu item that has no URL (<caption> is replaced)
152
   protected  $menuHeaderString = '<li class="menu_header_<level>"><caption></li>';
153
   // string which is placed around <menublock>, ie this goes around a menu/submenu. <level> can be used to determine
154
   // which level (zero based) we are in the menu (level = 0 is top menu)
155
   protected  $menuBlockString = '<ul class="menu"><menublock></ul>';
156
 
157
   // simply pass fields on to DBHierarchicalHash so it can load and parse the table
158
   public function __construct ($tableName = 'menu', $idFieldName = 'id', $parentFieldName = 'parent_id' ) {
159
      parent::__construct($tableName, $idFieldName, $parentFieldName );
160
   }
161
 
162
   // simple setter/getter for the caption column name in table
163
   public function captionColumnName ( $newValue = '' ) {
164
      if ($newValue) {
165
         $this->captionName = $newValue;
166
      }
167
      return $this->captionName;
168
   }
169
 
170
   // simple setter/getter for url column name in table
171
   public function urlColumnName ( $newValue = '' ) {
172
      if ($newValue) {
173
         $this->urlName = $newValue;
174
      }
175
      return $this->urlName;
176
   }
177
 
178
   // simple setter/getter for menuItemString for output
179
   public function menuItemString ( $newValue = '' ) {
180
      if ($newValue) {
181
         $this->menuItemString = $newValue;
182
      }
183
      return $this->menuItemString;
184
   }
185
 
186
   // simple setter/getter for menuHeaderString for output
187
   public function menuHeaderString ( $newValue = '' ) {
188
      if ($newValue) {
189
         $this->menuHeaderString = $newValue;
190
      }
191
      return $this->menuHeaderString;
192
   }
193
 
194
   // simple setter/getter for menu block string for output
195
   public function menuBlockString ( $newValue = '' ) {
196
      if ($newValue) {
197
         $this->menuBlockString = $newValue;
198
      }
199
      return $this->menuBlockString;
200
   }
201
 
202
   // just an entry point to displayMenu, with root of inputRecords and level 0
203
   function DBMenu2String () {
204
      return $this->htmlMenu( $this->inputRecords, 0 );
205
   }
206
 
207
   // function takes a menu level and creates an HTML Menu items from it. If a node has children, will
208
   // recursively call itself for each child node.
209
   private function htmlMenu ($menu, $level=0 ) {
210
      $result = '';
211
      foreach ($menu as $key => $value) { // process each array entry
212
         if ($value[$this->urlName]) { // this is a link, so it is a live menu option
213
            $result .= insertValuesIntoQuery( $this->menuItemString, array( 'url' => $value[$this->urlName], 'caption' => $value[$this->captionName], 'level' => $level) );
214
         } else { // not a link, so just create the text
215
            $result .= insertValuesIntoQuery( $this->menuHeaderString, array( 'caption' => $value[$this->captionName], 'level' => $level) );
216
         }
217
         if ( isset($value['children'])) { // if it has children, process them
218
            $result .=  $this->htmlMenu($value['children'], $level+1);
219
         }
220
      }
221
      // place the block code around the menu, and return the result
222
      return insertValuesIntoQuery( $this->menuBlockString, array( 'menublock' => $result, 'level' => $level ) );
223
   }
224
} // class DBMenu
225
 
226
/* following block is for testing. comment out for production */
227
//$menu = new DBHierarchicalHash ('menu', 'menu_id');
228
//print_r( $menu );
229
 
230
//$menu = new DBMenu( 'menu', 'menu_id' );
231
//print $menu->DBMenu2String();
232
//print "Menu Structure:\n"; print_r($menu);
233
 
234
/* end of block for testing */
235
 
236
?>