| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * A class for displaying various tree-like structures. 4 * 5 * Extend the Walker class to use it, see examples below. Child classes 6 * do not need to implement all of the abstract methods in the class. The child 7 * only needs to implement the methods that are needed. 8 * 9 * @since 2.1.0 10 * 11 * @package WordPress 12 * @abstract 13 */ 14 #[AllowDynamicProperties] 15 class Walker { 16 /** 17 * What the class handles. 18 * 19 * @since 2.1.0 20 * @var string 21 */ 22 public $tree_type; 23 24 /** 25 * DB fields to use. 26 * 27 * @since 2.1.0 28 * @var string[] 29 */ 30 public $db_fields; 31 32 /** 33 * Max number of pages walked by the paged walker. 34 * 35 * @since 2.7.0 36 * @var int 37 */ 38 public $max_pages = 1; 39 40 /** 41 * Whether the current element has children or not. 42 * 43 * To be used in start_el(). 44 * 45 * @since 4.0.0 46 * @var bool 47 */ 48 public $has_children; 49 50 /** 51 * Starts the list before the elements are added. 52 * 53 * The $args parameter holds additional values that may be used with the child 54 * class methods. This method is called at the start of the output list. 55 * 56 * @since 2.1.0 57 * @abstract 58 * 59 * @param string $output Used to append additional content (passed by reference). 60 * @param int $depth Depth of the item. 61 * @param array $args An array of additional arguments. 62 */ 63 public function start_lvl( &$output, $depth = 0, $args = array() ) {} 64 65 /** 66 * Ends the list of after the elements are added. 67 * 68 * The $args parameter holds additional values that may be used with the child 69 * class methods. This method finishes the list at the end of output of the elements. 70 * 71 * @since 2.1.0 72 * @abstract 73 * 74 * @param string $output Used to append additional content (passed by reference). 75 * @param int $depth Depth of the item. 76 * @param array $args An array of additional arguments. 77 */ 78 public function end_lvl( &$output, $depth = 0, $args = array() ) {} 79 80 /** 81 * Starts the element output. 82 * 83 * The $args parameter holds additional values that may be used with the child 84 * class methods. Also includes the element output. 85 * 86 * @since 2.1.0 87 * @since 5.9.0 Renamed `$object` (a PHP reserved keyword) to `$data_object` for PHP 8 named parameter support. 88 * @abstract 89 * 90 * @param string $output Used to append additional content (passed by reference). 91 * @param object $data_object The data object. 92 * @param int $depth Depth of the item. 93 * @param array $args An array of additional arguments. 94 * @param int $current_object_id Optional. ID of the current item. Default 0. 95 */ 96 public function start_el( &$output, $data_object, $depth = 0, $args = array(), $current_object_id = 0 ) {} 97 98 /** 99 * Ends the element output, if needed. 100 * 101 * The $args parameter holds additional values that may be used with the child class methods. 102 * 103 * @since 2.1.0 104 * @since 5.9.0 Renamed `$object` (a PHP reserved keyword) to `$data_object` for PHP 8 named parameter support. 105 * @abstract 106 * 107 * @param string $output Used to append additional content (passed by reference). 108 * @param object $data_object The data object. 109 * @param int $depth Depth of the item. 110 * @param array $args An array of additional arguments. 111 */ 112 public function end_el( &$output, $data_object, $depth = 0, $args = array() ) {} 113 114 /** 115 * Traverses elements to create list from elements. 116 * 117 * Display one element if the element doesn't have any children otherwise, 118 * display the element and its children. Will only traverse up to the max 119 * depth and no ignore elements under that depth. It is possible to set the 120 * max depth to include all depths, see walk() method. 121 * 122 * This method should not be called directly, use the walk() method instead. 123 * 124 * @since 2.5.0 125 * 126 * @param object $element Data object. 127 * @param array $children_elements List of elements to continue traversing (passed by reference). 128 * @param int $max_depth Max depth to traverse. 129 * @param int $depth Depth of current element. 130 * @param array $args An array of arguments. 131 * @param string $output Used to append additional content (passed by reference). 132 */ 133 public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) { 134 if ( ! $element ) { 135 return; 136 } 137 138 $max_depth = (int) $max_depth; 139 $depth = (int) $depth; 140 141 $id_field = $this->db_fields['id']; 142 $id = $element->$id_field; 143 144 // Display this element. 145 $this->has_children = ! empty( $children_elements[ $id ] ); 146 if ( isset( $args[0] ) && is_array( $args[0] ) ) { 147 $args[0]['has_children'] = $this->has_children; // Back-compat. 148 } 149 150 $this->start_el( $output, $element, $depth, ...array_values( $args ) ); 151 152 // Descend only when the depth is right and there are children for this element. 153 if ( ( 0 === $max_depth || $max_depth > $depth + 1 ) && isset( $children_elements[ $id ] ) ) { 154 155 foreach ( $children_elements[ $id ] as $child ) { 156 157 if ( ! isset( $newlevel ) ) { 158 $newlevel = true; 159 // Start the child delimiter. 160 $this->start_lvl( $output, $depth, ...array_values( $args ) ); 161 } 162 $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output ); 163 } 164 unset( $children_elements[ $id ] ); 165 } 166 167 if ( isset( $newlevel ) && $newlevel ) { 168 // End the child delimiter. 169 $this->end_lvl( $output, $depth, ...array_values( $args ) ); 170 } 171 172 // End this element. 173 $this->end_el( $output, $element, $depth, ...array_values( $args ) ); 174 } 175 176 /** 177 * Displays array of elements hierarchically. 178 * 179 * Does not assume any existing order of elements. 180 * 181 * $max_depth = -1 means flatly display every element. 182 * $max_depth = 0 means display all levels. 183 * $max_depth > 0 specifies the number of display levels. 184 * 185 * @since 2.1.0 186 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it 187 * to the function signature. 188 * 189 * @param array $elements An array of elements. 190 * @param int $max_depth The maximum hierarchical depth. 191 * @param mixed ...$args Optional additional arguments. 192 * @return string The hierarchical item output. 193 */ 194 public function walk( $elements, $max_depth, ...$args ) { 195 $output = ''; 196 197 $max_depth = (int) $max_depth; 198 199 // Invalid parameter or nothing to walk. 200 if ( $max_depth < -1 || empty( $elements ) ) { 201 return $output; 202 } 203 204 $parent_field = $this->db_fields['parent']; 205 206 // Flat display. 207 if ( -1 === $max_depth ) { 208 $empty_array = array(); 209 foreach ( $elements as $e ) { 210 $this->display_element( $e, $empty_array, 1, 0, $args, $output ); 211 } 212 return $output; 213 } 214 215 /* 216 * Need to display in hierarchical order. 217 * Separate elements into two buckets: top level and children elements. 218 * Children_elements is two dimensional array. Example: 219 * Children_elements[10][] contains all sub-elements whose parent is 10. 220 */ 221 $top_level_elements = array(); 222 $children_elements = array(); 223 foreach ( $elements as $e ) { 224 if ( empty( $e->$parent_field ) ) { 225 $top_level_elements[] = $e; 226 } else { 227 $children_elements[ $e->$parent_field ][] = $e; 228 } 229 } 230 231 /* 232 * When none of the elements is top level. 233 * Assume the first one must be root of the sub elements. 234 */ 235 if ( empty( $top_level_elements ) ) { 236 237 $root = array_first( $elements ); 238 239 $top_level_elements = array(); 240 $children_elements = array(); 241 foreach ( $elements as $e ) { 242 if ( $root->$parent_field === $e->$parent_field ) { 243 $top_level_elements[] = $e; 244 } else { 245 $children_elements[ $e->$parent_field ][] = $e; 246 } 247 } 248 } 249 250 foreach ( $top_level_elements as $e ) { 251 $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); 252 } 253 254 /* 255 * If we are displaying all levels, and remaining children_elements is not empty, 256 * then we got orphans, which should be displayed regardless. 257 */ 258 if ( ( 0 === $max_depth ) && count( $children_elements ) > 0 ) { 259 $empty_array = array(); 260 foreach ( $children_elements as $orphans ) { 261 foreach ( $orphans as $op ) { 262 $this->display_element( $op, $empty_array, 1, 0, $args, $output ); 263 } 264 } 265 } 266 267 return $output; 268 } 269 270 /** 271 * Produces a page of nested elements. 272 * 273 * Given an array of hierarchical elements, the maximum depth, a specific page number, 274 * and number of elements per page, this function first determines all top level root elements 275 * belonging to that page, then lists them and all of their children in hierarchical order. 276 * 277 * $max_depth = 0 means display all levels. 278 * $max_depth > 0 specifies the number of display levels. 279 * 280 * @since 2.7.0 281 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it 282 * to the function signature. 283 * 284 * @param array $elements An array of elements. 285 * @param int $max_depth The maximum hierarchical depth. 286 * @param int $page_num The specific page number, beginning with 1. 287 * @param int $per_page Number of elements per page. 288 * @param mixed ...$args Optional additional arguments. 289 * @return string XHTML of the specified page of elements. 290 */ 291 public function paged_walk( $elements, $max_depth, $page_num, $per_page, ...$args ) { 292 $output = ''; 293 294 $max_depth = (int) $max_depth; 295 296 if ( empty( $elements ) || $max_depth < -1 ) { 297 return $output; 298 } 299 300 $parent_field = $this->db_fields['parent']; 301 302 $count = -1; 303 if ( -1 === $max_depth ) { 304 $total_top = count( $elements ); 305 } 306 if ( $page_num < 1 || $per_page < 0 ) { 307 // No paging. 308 $paging = false; 309 $start = 0; 310 if ( -1 === $max_depth ) { 311 $end = $total_top; 312 } 313 $this->max_pages = 1; 314 } else { 315 $paging = true; 316 $start = ( (int) $page_num - 1 ) * (int) $per_page; 317 $end = $start + $per_page; 318 if ( -1 === $max_depth ) { 319 $this->max_pages = (int) ceil( $total_top / $per_page ); 320 } 321 } 322 323 // Flat display. 324 if ( -1 === $max_depth ) { 325 if ( ! empty( $args[0]['reverse_top_level'] ) ) { 326 $elements = array_reverse( $elements ); 327 $oldstart = $start; 328 $start = $total_top - $end; 329 $end = $total_top - $oldstart; 330 } 331 332 $empty_array = array(); 333 foreach ( $elements as $e ) { 334 ++$count; 335 if ( $count < $start ) { 336 continue; 337 } 338 if ( $count >= $end ) { 339 break; 340 } 341 $this->display_element( $e, $empty_array, 1, 0, $args, $output ); 342 } 343 return $output; 344 } 345 346 /* 347 * Separate elements into two buckets: top level and children elements. 348 * Children_elements is two dimensional array, e.g. 349 * $children_elements[10][] contains all sub-elements whose parent is 10. 350 */ 351 $top_level_elements = array(); 352 $children_elements = array(); 353 foreach ( $elements as $e ) { 354 if ( empty( $e->$parent_field ) ) { 355 $top_level_elements[] = $e; 356 } else { 357 $children_elements[ $e->$parent_field ][] = $e; 358 } 359 } 360 361 $total_top = count( $top_level_elements ); 362 if ( $paging ) { 363 $this->max_pages = (int) ceil( $total_top / $per_page ); 364 } else { 365 $end = $total_top; 366 } 367 368 if ( ! empty( $args[0]['reverse_top_level'] ) ) { 369 $top_level_elements = array_reverse( $top_level_elements ); 370 $oldstart = $start; 371 $start = $total_top - $end; 372 $end = $total_top - $oldstart; 373 } 374 if ( ! empty( $args[0]['reverse_children'] ) ) { 375 foreach ( $children_elements as $parent => $children ) { 376 $children_elements[ $parent ] = array_reverse( $children ); 377 } 378 } 379 380 foreach ( $top_level_elements as $e ) { 381 ++$count; 382 383 // For the last page, need to unset earlier children in order to keep track of orphans. 384 if ( $end >= $total_top && $count < $start ) { 385 $this->unset_children( $e, $children_elements ); 386 } 387 388 if ( $count < $start ) { 389 continue; 390 } 391 392 if ( $count >= $end ) { 393 break; 394 } 395 396 $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); 397 } 398 399 if ( $end >= $total_top && count( $children_elements ) > 0 ) { 400 $empty_array = array(); 401 foreach ( $children_elements as $orphans ) { 402 foreach ( $orphans as $op ) { 403 $this->display_element( $op, $empty_array, 1, 0, $args, $output ); 404 } 405 } 406 } 407 408 return $output; 409 } 410 411 /** 412 * Calculates the total number of root elements. 413 * 414 * @since 2.7.0 415 * 416 * @param array $elements Elements to list. 417 * @return int Number of root elements. 418 */ 419 public function get_number_of_root_elements( $elements ) { 420 $num = 0; 421 $parent_field = $this->db_fields['parent']; 422 423 foreach ( $elements as $e ) { 424 if ( empty( $e->$parent_field ) ) { 425 ++$num; 426 } 427 } 428 return $num; 429 } 430 431 /** 432 * Unsets all the children for a given top level element. 433 * 434 * @since 2.7.0 435 * 436 * @param object $element The top level element. 437 * @param array $children_elements The children elements. 438 */ 439 public function unset_children( $element, &$children_elements ) { 440 if ( ! $element || ! $children_elements ) { 441 return; 442 } 443 444 $id_field = $this->db_fields['id']; 445 $id = $element->$id_field; 446 447 if ( ! empty( $children_elements[ $id ] ) && is_array( $children_elements[ $id ] ) ) { 448 foreach ( (array) $children_elements[ $id ] as $child ) { 449 $this->unset_children( $child, $children_elements ); 450 } 451 } 452 453 unset( $children_elements[ $id ] ); 454 } 455 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Jul 16 08:20:16 2026 | Cross-referenced by PHPXref |