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
12 rodolico 94
               // this is more complex as a parent of null might be used. Thus, we check for a null and, if
95
               // it is used, we set it to the rootNodeValue
96
               if ($key == $this->parentFieldName && ! $thisRow[$key] ) {
97
                  $this->inputRecords[$thisRow[$this->keyField]][$key] = $this->rootNodevalue;
98
               } else {
99
                  $this->inputRecords[$thisRow[$this->keyField]][$key] = $thisRow[$key];
100
               }
2 rodolico 101
            }
102
         }
103
      }
104
      $this->inputRecords[$this->rootNodevalue] = array(); // create our empty root node
105
   } // function loadData
106
 
107
   # populates the hiearchy created in load
108
   # NOTE: if the current node also has children, it will recursively do those
109
   # main call is &copyChildren( rootNode )
110
   private function copyChildren($currentNode) {
111
      # for each child
112
      //print "Looking at: "; print_r($currentNode);
113
      if ( isset($currentNode['children']) ) {
114
         foreach ($currentNode['children'] as $key => $values) {
115
            # copy any children of this child table that may exist
116
            $this->inputRecords[$key] = $this->copyChildren( $this->inputRecords[$key] );
117
            # copy each value from the node to this sub-node
118
            foreach ($this->inputRecords[$key] as $child => $childValue) {
119
               if ($child != $this->parentFieldName ) {
120
                  $currentNode['children'][$key][$child] = $childValue;
121
               }
122
            }
123
         }
124
      }
125
      return $currentNode;
126
   } // function copyChildren
127
} // class DBHierarchicalHash
128
 
129
 
130
/*
131
   simple extension of DBHierarchicalHash allowing a menu structure to be stored in a database.
132
   the structure for the database is simple:
133
   create table menu (
134
      menu_id     int unsigned not null auto_increment,
135
      parent_id   int unsigned not null default 0,
136
      caption     varchar(20) not null,
137
      url         varchar(64),
138
      primary key (menu_id)
139
   )
140
   where caption is the string displayed on the menu, and url is the (possibly null) url
141
   to be called when the link is clicked by the user. The resulting line is something like:
142
      <a href="url">caption</a>
143
   though this can be modified by changing $menuItemString and $menuHeaderString (see below)
144
 
145
   menu_id is a unique identifier for a single row, and parent_id points to the menu that it
146
   is a submenu of (0 indicates a root menu item)
147
 
148
   Note: I tried to avoid any constants in the class, so column names, menu_id data type, root
149
         menu indicator are all modifiable
150
*/
151
class DBMenu extends DBHierarchicalHash {
152
 
153
   protected  $captionName = 'caption'; // column name for display string in database
154
   protected  $urlName = 'url';         // column name for URL field in database
155
   // string which is searched/replaced for a menu item that has a URL (<url> and <caption> are replaced)
156
   protected  $menuItemString = '<li class="menu_item_<level>"><a href="<url>"><caption></a></li>';
157
   // string which is searched/replaced for a menu item that has no URL (<caption> is replaced)
158
   protected  $menuHeaderString = '<li class="menu_header_<level>"><caption></li>';
159
   // string which is placed around <menublock>, ie this goes around a menu/submenu. <level> can be used to determine
160
   // which level (zero based) we are in the menu (level = 0 is top menu)
161
   protected  $menuBlockString = '<ul class="menu"><menublock></ul>';
162
 
163
   // simply pass fields on to DBHierarchicalHash so it can load and parse the table
164
   public function __construct ($tableName = 'menu', $idFieldName = 'id', $parentFieldName = 'parent_id' ) {
165
      parent::__construct($tableName, $idFieldName, $parentFieldName );
166
   }
167
 
168
   // simple setter/getter for the caption column name in table
169
   public function captionColumnName ( $newValue = '' ) {
170
      if ($newValue) {
171
         $this->captionName = $newValue;
172
      }
173
      return $this->captionName;
174
   }
175
 
176
   // simple setter/getter for url column name in table
177
   public function urlColumnName ( $newValue = '' ) {
178
      if ($newValue) {
179
         $this->urlName = $newValue;
180
      }
181
      return $this->urlName;
182
   }
183
 
184
   // simple setter/getter for menuItemString for output
185
   public function menuItemString ( $newValue = '' ) {
186
      if ($newValue) {
187
         $this->menuItemString = $newValue;
188
      }
189
      return $this->menuItemString;
190
   }
191
 
192
   // simple setter/getter for menuHeaderString for output
193
   public function menuHeaderString ( $newValue = '' ) {
194
      if ($newValue) {
195
         $this->menuHeaderString = $newValue;
196
      }
197
      return $this->menuHeaderString;
198
   }
199
 
200
   // simple setter/getter for menu block string for output
201
   public function menuBlockString ( $newValue = '' ) {
202
      if ($newValue) {
203
         $this->menuBlockString = $newValue;
204
      }
205
      return $this->menuBlockString;
206
   }
207
 
208
   // just an entry point to displayMenu, with root of inputRecords and level 0
19 rodolico 209
   function DBMenu2String ( $rootDir = '' ) {
210
      return $this->htmlMenu( $this->inputRecords, 0, $rootDir );
2 rodolico 211
   }
212
 
213
   // function takes a menu level and creates an HTML Menu items from it. If a node has children, will
214
   // recursively call itself for each child node.
19 rodolico 215
   private function htmlMenu ($menu, $level=0, $rootDir = '' ) {
2 rodolico 216
      $result = '';
217
      foreach ($menu as $key => $value) { // process each array entry
218
         if ($value[$this->urlName]) { // this is a link, so it is a live menu option
19 rodolico 219
            $result .= insertValuesIntoQuery( $this->menuItemString, array( 'url' => $rootDir . $value[$this->urlName], 'caption' => $value[$this->captionName], 'level' => $level) );
2 rodolico 220
         } else { // not a link, so just create the text
221
            $result .= insertValuesIntoQuery( $this->menuHeaderString, array( 'caption' => $value[$this->captionName], 'level' => $level) );
222
         }
223
         if ( isset($value['children'])) { // if it has children, process them
24 rodolico 224
            $result .=  $this->htmlMenu($value['children'], $level+1, $rootDir);
2 rodolico 225
         }
226
      }
227
      // place the block code around the menu, and return the result
228
      return insertValuesIntoQuery( $this->menuBlockString, array( 'menublock' => $result, 'level' => $level ) );
229
   }
230
} // class DBMenu
231
 
232
/* following block is for testing. comment out for production */
233
//$menu = new DBHierarchicalHash ('menu', 'menu_id');
234
//print_r( $menu );
235
 
236
//$menu = new DBMenu( 'menu', 'menu_id' );
237
//print $menu->DBMenu2String();
238
//print "Menu Structure:\n"; print_r($menu);
239
 
240
/* end of block for testing */
241
 
242
?>