Subversion Repositories php_library

Rev

Rev 51 | 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
50 rodolico 45
   protected $query; // the query used to get the records
2 rodolico 46
 
48 rodolico 47
   public function __construct( $tableName, $keyField = 'id', $parentFieldName = 'parent_id', $rootNodeValue = 0, $recordList = null ) {
2 rodolico 48
      // standard variable load
49
      $this->tableName = $tableName;
50
      $this->keyField = $keyField;
51
      $this->parentFieldName = $parentFieldName;
52
      $this->rootNodevalue = $rootNodeValue;
48 rodolico 53
      $this->recordList = $recordList;
2 rodolico 54
      // load the data from the database into memory
55
      // upon completion, will have data in a single hash of hashes (not hiearchy)
56
      // will also create an emtpy entry for "element 0"
57
      $this->loadData();
58
      // find the children of each node
59
      $this->findChildren();
60
      // now that we know who all the children are, let's actually make them part of the structure
61
      $this->inputRecords = $this->copyChildren($this->inputRecords[$this->rootNodevalue]);
62
      // all we care about are the children of the root node, so simply assign that to inputRecords
63
      $this->inputRecords = $this->inputRecords['children'];
64
   } // function __construct
65
 
66
 
67
   /*
68
      Goes through each node and reverses the "who is my parent" to the parent's "who are my children"
69
      It does not populate the children; it simply creates an empty array for each child to be processed
70
      later.
71
   */
72
   private function findChildren () {
73
      // following loop takes inputRecords and creates (empty) elements for each child
74
      foreach ($this->inputRecords as $key => $values) { // go through every node
75
         if ( isset ($this->inputRecords[$key][$this->parentFieldName]) ) { // if it has a parent
76
            // tell parent about itself
77
            $this->inputRecords[$values[$this->parentFieldName]]['children'][$key] = array();
78
         }
79
      }
80
   } // function findChildren
81
 
82
   /* 
83
      simply returns inputRecords for processing by calling routine. This is the goal of this class, to create
84
      this structure from the database structure defined above
85
   */
86
   public function toHash () {
87
      return $this->inputRecords;
88
   } // function toHash
89
 
90
   /* 
91
      loads data from database. The column defined in $this->keyField will become the index into the hash, and all
92
      additional columns will simply be added to the array pointed by it.
93
      An additional entry in the table will be created with $this->rootNodeValue as its index, to allow root level
94
      items to have a parent.
95
   */
96
   private function loadData ( ) {
50 rodolico 97
      $this->query = "select * from $this->tableName";
49 rodolico 98
      if ( ! is_null( $this->recordList ) )
52 rodolico 99
         $this->query .= " where $this->keyField in (" . implode( ',',$this->recordList ) . ')';
50 rodolico 100
      $inputRows = queryDatabaseExtended( $this->query ); // get everything from the table
2 rodolico 101
      $inputRows = $inputRows['data']; // we only care about the data
102
      $this->inputRecords = array(); // initialize inputRecords to empty
103
      // main loop, will read the query results in one row at a time
104
      foreach ( $inputRows as $thisRow ) {
105
         foreach ($thisRow as $key => $values) { // copy each value
106
            if ($key != $this->keyField) { // unless it is the key field
12 rodolico 107
               // this is more complex as a parent of null might be used. Thus, we check for a null and, if
108
               // it is used, we set it to the rootNodeValue
109
               if ($key == $this->parentFieldName && ! $thisRow[$key] ) {
110
                  $this->inputRecords[$thisRow[$this->keyField]][$key] = $this->rootNodevalue;
111
               } else {
112
                  $this->inputRecords[$thisRow[$this->keyField]][$key] = $thisRow[$key];
113
               }
2 rodolico 114
            }
115
         }
116
      }
117
      $this->inputRecords[$this->rootNodevalue] = array(); // create our empty root node
118
   } // function loadData
119
 
120
   # populates the hiearchy created in load
121
   # NOTE: if the current node also has children, it will recursively do those
122
   # main call is &copyChildren( rootNode )
123
   private function copyChildren($currentNode) {
124
      # for each child
125
      //print "Looking at: "; print_r($currentNode);
126
      if ( isset($currentNode['children']) ) {
127
         foreach ($currentNode['children'] as $key => $values) {
128
            # copy any children of this child table that may exist
129
            $this->inputRecords[$key] = $this->copyChildren( $this->inputRecords[$key] );
130
            # copy each value from the node to this sub-node
131
            foreach ($this->inputRecords[$key] as $child => $childValue) {
132
               if ($child != $this->parentFieldName ) {
133
                  $currentNode['children'][$key][$child] = $childValue;
134
               }
135
            }
136
         }
137
      }
138
      return $currentNode;
139
   } // function copyChildren
140
} // class DBHierarchicalHash
141
 
142
 
143
/*
144
   simple extension of DBHierarchicalHash allowing a menu structure to be stored in a database.
145
   the structure for the database is simple:
146
   create table menu (
147
      menu_id     int unsigned not null auto_increment,
148
      parent_id   int unsigned not null default 0,
149
      caption     varchar(20) not null,
150
      url         varchar(64),
151
      primary key (menu_id)
152
   )
153
   where caption is the string displayed on the menu, and url is the (possibly null) url
154
   to be called when the link is clicked by the user. The resulting line is something like:
155
      <a href="url">caption</a>
156
   though this can be modified by changing $menuItemString and $menuHeaderString (see below)
157
 
158
   menu_id is a unique identifier for a single row, and parent_id points to the menu that it
159
   is a submenu of (0 indicates a root menu item)
160
 
161
   Note: I tried to avoid any constants in the class, so column names, menu_id data type, root
162
         menu indicator are all modifiable
163
*/
164
class DBMenu extends DBHierarchicalHash {
165
 
166
   protected  $captionName = 'caption'; // column name for display string in database
167
   protected  $urlName = 'url';         // column name for URL field in database
168
   // string which is searched/replaced for a menu item that has a URL (<url> and <caption> are replaced)
169
   protected  $menuItemString = '<li class="menu_item_<level>"><a href="<url>"><caption></a></li>';
170
   // string which is searched/replaced for a menu item that has no URL (<caption> is replaced)
171
   protected  $menuHeaderString = '<li class="menu_header_<level>"><caption></li>';
172
   // string which is placed around <menublock>, ie this goes around a menu/submenu. <level> can be used to determine
173
   // which level (zero based) we are in the menu (level = 0 is top menu)
174
   protected  $menuBlockString = '<ul class="menu"><menublock></ul>';
175
 
176
   // simply pass fields on to DBHierarchicalHash so it can load and parse the table
48 rodolico 177
   public function __construct ($tableName = 'menu', $idFieldName = 'id', $parentFieldName = 'parent_id', $recordList = null ) {
51 rodolico 178
      parent::__construct($tableName, $idFieldName, $parentFieldName, 0, $recordList );
2 rodolico 179
   }
180
 
181
   // simple setter/getter for the caption column name in table
182
   public function captionColumnName ( $newValue = '' ) {
183
      if ($newValue) {
184
         $this->captionName = $newValue;
185
      }
186
      return $this->captionName;
187
   }
188
 
189
   // simple setter/getter for url column name in table
190
   public function urlColumnName ( $newValue = '' ) {
191
      if ($newValue) {
192
         $this->urlName = $newValue;
193
      }
194
      return $this->urlName;
195
   }
196
 
197
   // simple setter/getter for menuItemString for output
198
   public function menuItemString ( $newValue = '' ) {
199
      if ($newValue) {
200
         $this->menuItemString = $newValue;
201
      }
202
      return $this->menuItemString;
203
   }
204
 
205
   // simple setter/getter for menuHeaderString for output
206
   public function menuHeaderString ( $newValue = '' ) {
207
      if ($newValue) {
208
         $this->menuHeaderString = $newValue;
209
      }
210
      return $this->menuHeaderString;
211
   }
212
 
213
   // simple setter/getter for menu block string for output
214
   public function menuBlockString ( $newValue = '' ) {
215
      if ($newValue) {
216
         $this->menuBlockString = $newValue;
217
      }
218
      return $this->menuBlockString;
219
   }
220
 
221
   // just an entry point to displayMenu, with root of inputRecords and level 0
19 rodolico 222
   function DBMenu2String ( $rootDir = '' ) {
223
      return $this->htmlMenu( $this->inputRecords, 0, $rootDir );
2 rodolico 224
   }
225
 
226
   // function takes a menu level and creates an HTML Menu items from it. If a node has children, will
227
   // recursively call itself for each child node.
19 rodolico 228
   private function htmlMenu ($menu, $level=0, $rootDir = '' ) {
2 rodolico 229
      $result = '';
230
      foreach ($menu as $key => $value) { // process each array entry
231
         if ($value[$this->urlName]) { // this is a link, so it is a live menu option
19 rodolico 232
            $result .= insertValuesIntoQuery( $this->menuItemString, array( 'url' => $rootDir . $value[$this->urlName], 'caption' => $value[$this->captionName], 'level' => $level) );
2 rodolico 233
         } else { // not a link, so just create the text
234
            $result .= insertValuesIntoQuery( $this->menuHeaderString, array( 'caption' => $value[$this->captionName], 'level' => $level) );
235
         }
236
         if ( isset($value['children'])) { // if it has children, process them
24 rodolico 237
            $result .=  $this->htmlMenu($value['children'], $level+1, $rootDir);
2 rodolico 238
         }
239
      }
240
      // place the block code around the menu, and return the result
241
      return insertValuesIntoQuery( $this->menuBlockString, array( 'menublock' => $result, 'level' => $level ) );
242
   }
243
} // class DBMenu
244
 
245
/* following block is for testing. comment out for production */
246
//$menu = new DBHierarchicalHash ('menu', 'menu_id');
247
//print_r( $menu );
248
 
249
//$menu = new DBMenu( 'menu', 'menu_id' );
250
//print $menu->DBMenu2String();
251
//print "Menu Structure:\n"; print_r($menu);
252
 
253
/* end of block for testing */
254
 
48 rodolico 255
?>