| [ 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 ) ) { 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 * Find an element whose parent is not in the list to use as the root. 234 */ 235 if ( empty( $top_level_elements ) ) { 236 $id_field = $this->db_fields['id']; 237 $element_ids = array(); 238 foreach ( $elements as $e ) { 239 $element_ids[ $e->$id_field ] = true; 240 } 241 242 // Fall back to the first element when all parents are included, as with a cycle. 243 $root = array_first( $elements ); 244 foreach ( $elements as $e ) { 245 if ( ! isset( $element_ids[ $e->$parent_field ] ) ) { 246 $root = $e; 247 break; 248 } 249 } 250 251 $top_level_elements = array(); 252 $children_elements = array(); 253 foreach ( $elements as $e ) { 254 if ( $root->$parent_field === $e->$parent_field ) { 255 $top_level_elements[] = $e; 256 } else { 257 $children_elements[ $e->$parent_field ][] = $e; 258 } 259 } 260 } 261 262 foreach ( $top_level_elements as $e ) { 263 $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); 264 } 265 266 /* 267 * If we are displaying all levels, and remaining children_elements is not empty, 268 * then we got orphans, which should be displayed regardless. 269 */ 270 if ( ( 0 === $max_depth ) && count( $children_elements ) > 0 ) { 271 $empty_array = array(); 272 foreach ( $children_elements as $orphans ) { 273 foreach ( $orphans as $op ) { 274 $this->display_element( $op, $empty_array, 1, 0, $args, $output ); 275 } 276 } 277 } 278 279 return $output; 280 } 281 282 /** 283 * Produces a page of nested elements. 284 * 285 * Given an array of hierarchical elements, the maximum depth, a specific page number, 286 * and number of elements per page, this function first determines all top level root elements 287 * belonging to that page, then lists them and all of their children in hierarchical order. 288 * 289 * $max_depth = 0 means display all levels. 290 * $max_depth > 0 specifies the number of display levels. 291 * 292 * @since 2.7.0 293 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it 294 * to the function signature. 295 * 296 * @param array $elements An array of elements. 297 * @param int $max_depth The maximum hierarchical depth. 298 * @param int $page_num The specific page number, beginning with 1. 299 * @param int $per_page Number of elements per page. 300 * @param mixed ...$args Optional additional arguments. 301 * @return string XHTML of the specified page of elements. 302 */ 303 public function paged_walk( $elements, $max_depth, $page_num, $per_page, ...$args ) { 304 $output = ''; 305 306 $max_depth = (int) $max_depth; 307 308 if ( empty( $elements ) || $max_depth < -1 ) { 309 return $output; 310 } 311 312 $parent_field = $this->db_fields['parent']; 313 314 $count = -1; 315 if ( -1 === $max_depth ) { 316 $total_top = count( $elements ); 317 } 318 if ( $page_num < 1 || $per_page < 0 ) { 319 // No paging. 320 $paging = false; 321 $start = 0; 322 if ( -1 === $max_depth ) { 323 $end = $total_top; 324 } 325 $this->max_pages = 1; 326 } else { 327 $paging = true; 328 $start = ( (int) $page_num - 1 ) * (int) $per_page; 329 $end = $start + $per_page; 330 if ( -1 === $max_depth ) { 331 $this->max_pages = (int) ceil( $total_top / $per_page ); 332 } 333 } 334 335 // Flat display. 336 if ( -1 === $max_depth ) { 337 if ( ! empty( $args[0]['reverse_top_level'] ) ) { 338 $elements = array_reverse( $elements ); 339 $oldstart = $start; 340 $start = $total_top - $end; 341 $end = $total_top - $oldstart; 342 } 343 344 $empty_array = array(); 345 foreach ( $elements as $e ) { 346 ++$count; 347 if ( $count < $start ) { 348 continue; 349 } 350 if ( $count >= $end ) { 351 break; 352 } 353 $this->display_element( $e, $empty_array, 1, 0, $args, $output ); 354 } 355 return $output; 356 } 357 358 /* 359 * Separate elements into two buckets: top level and children elements. 360 * Children_elements is two dimensional array, e.g. 361 * $children_elements[10][] contains all sub-elements whose parent is 10. 362 */ 363 $top_level_elements = array(); 364 $children_elements = array(); 365 foreach ( $elements as $e ) { 366 if ( empty( $e->$parent_field ) ) { 367 $top_level_elements[] = $e; 368 } else { 369 $children_elements[ $e->$parent_field ][] = $e; 370 } 371 } 372 373 $total_top = count( $top_level_elements ); 374 if ( $paging ) { 375 $this->max_pages = (int) ceil( $total_top / $per_page ); 376 } else { 377 $end = $total_top; 378 } 379 380 if ( ! empty( $args[0]['reverse_top_level'] ) ) { 381 $top_level_elements = array_reverse( $top_level_elements ); 382 $oldstart = $start; 383 $start = $total_top - $end; 384 $end = $total_top - $oldstart; 385 } 386 if ( ! empty( $args[0]['reverse_children'] ) ) { 387 foreach ( $children_elements as $parent => $children ) { 388 $children_elements[ $parent ] = array_reverse( $children ); 389 } 390 } 391 392 foreach ( $top_level_elements as $e ) { 393 ++$count; 394 395 // For the last page, need to unset earlier children in order to keep track of orphans. 396 if ( $end >= $total_top && $count < $start ) { 397 $this->unset_children( $e, $children_elements ); 398 } 399 400 if ( $count < $start ) { 401 continue; 402 } 403 404 if ( $count >= $end ) { 405 break; 406 } 407 408 $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); 409 } 410 411 if ( $end >= $total_top && count( $children_elements ) > 0 ) { 412 $empty_array = array(); 413 foreach ( $children_elements as $orphans ) { 414 foreach ( $orphans as $op ) { 415 $this->display_element( $op, $empty_array, 1, 0, $args, $output ); 416 } 417 } 418 } 419 420 return $output; 421 } 422 423 /** 424 * Calculates the total number of root elements. 425 * 426 * @since 2.7.0 427 * 428 * @param array $elements Elements to list. 429 * @return int Number of root elements. 430 */ 431 public function get_number_of_root_elements( $elements ) { 432 $num = 0; 433 $parent_field = $this->db_fields['parent']; 434 435 foreach ( $elements as $e ) { 436 if ( empty( $e->$parent_field ) ) { 437 ++$num; 438 } 439 } 440 return $num; 441 } 442 443 /** 444 * Unsets all the children for a given top level element. 445 * 446 * @since 2.7.0 447 * 448 * @param object $element The top level element. 449 * @param array $children_elements The children elements. 450 */ 451 public function unset_children( $element, &$children_elements ) { 452 if ( ! $element || ! $children_elements ) { 453 return; 454 } 455 456 $id_field = $this->db_fields['id']; 457 $id = $element->$id_field; 458 459 if ( ! empty( $children_elements[ $id ] ) && is_array( $children_elements[ $id ] ) ) { 460 foreach ( (array) $children_elements[ $id ] as $child ) { 461 $this->unset_children( $child, $children_elements ); 462 } 463 } 464 465 unset( $children_elements[ $id ] ); 466 } 467 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Nov 4 08:20:38 2026 | Cross-referenced by PHPXref |