Subversion Repositories php_library

Rev

Rev 47 | Rev 49 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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