[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-walker.php (source)

   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  }


Generated : Thu Jul 16 08:20:16 2026 Cross-referenced by PHPXref