[ 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          $id_field = $this->db_fields['id'];
 139          $id       = $element->$id_field;
 140  
 141          // Display this element.
 142          $this->has_children = ! empty( $children_elements[ $id ] );
 143          if ( isset( $args[0] ) && is_array( $args[0] ) ) {
 144              $args[0]['has_children'] = $this->has_children; // Back-compat.
 145          }
 146  
 147          $this->start_el( $output, $element, $depth, ...array_values( $args ) );
 148  
 149          // Descend only when the depth is right and there are children for this element.
 150          if ( ( 0 === $max_depth || $max_depth > $depth + 1 ) && isset( $children_elements[ $id ] ) ) {
 151  
 152              foreach ( $children_elements[ $id ] as $child ) {
 153  
 154                  if ( ! isset( $newlevel ) ) {
 155                      $newlevel = true;
 156                      // Start the child delimiter.
 157                      $this->start_lvl( $output, $depth, ...array_values( $args ) );
 158                  }
 159                  $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output );
 160              }
 161              unset( $children_elements[ $id ] );
 162          }
 163  
 164          if ( isset( $newlevel ) && $newlevel ) {
 165              // End the child delimiter.
 166              $this->end_lvl( $output, $depth, ...array_values( $args ) );
 167          }
 168  
 169          // End this element.
 170          $this->end_el( $output, $element, $depth, ...array_values( $args ) );
 171      }
 172  
 173      /**
 174       * Displays array of elements hierarchically.
 175       *
 176       * Does not assume any existing order of elements.
 177       *
 178       * $max_depth = -1 means flatly display every element.
 179       * $max_depth = 0 means display all levels.
 180       * $max_depth > 0 specifies the number of display levels.
 181       *
 182       * @since 2.1.0
 183       * @since 5.3.0 Formalized the existing `...$args` parameter by adding it
 184       *              to the function signature.
 185       *
 186       * @param array $elements  An array of elements.
 187       * @param int   $max_depth The maximum hierarchical depth.
 188       * @param mixed ...$args   Optional additional arguments.
 189       * @return string The hierarchical item output.
 190       */
 191  	public function walk( $elements, $max_depth, ...$args ) {
 192          $output = '';
 193  
 194          // Invalid parameter or nothing to walk.
 195          if ( $max_depth < -1 || empty( $elements ) ) {
 196              return $output;
 197          }
 198  
 199          $parent_field = $this->db_fields['parent'];
 200  
 201          // Flat display.
 202          if ( -1 === $max_depth ) {
 203              $empty_array = array();
 204              foreach ( $elements as $e ) {
 205                  $this->display_element( $e, $empty_array, 1, 0, $args, $output );
 206              }
 207              return $output;
 208          }
 209  
 210          /*
 211           * Need to display in hierarchical order.
 212           * Separate elements into two buckets: top level and children elements.
 213           * Children_elements is two dimensional array. Example:
 214           * Children_elements[10][] contains all sub-elements whose parent is 10.
 215           */
 216          $top_level_elements = array();
 217          $children_elements  = array();
 218          foreach ( $elements as $e ) {
 219              if ( empty( $e->$parent_field ) ) {
 220                  $top_level_elements[] = $e;
 221              } else {
 222                  $children_elements[ $e->$parent_field ][] = $e;
 223              }
 224          }
 225  
 226          /*
 227           * When none of the elements is top level.
 228           * Assume the first one must be root of the sub elements.
 229           */
 230          if ( empty( $top_level_elements ) ) {
 231  
 232              $first = array_slice( $elements, 0, 1 );
 233              $root  = $first[0];
 234  
 235              $top_level_elements = array();
 236              $children_elements  = array();
 237              foreach ( $elements as $e ) {
 238                  if ( $root->$parent_field === $e->$parent_field ) {
 239                      $top_level_elements[] = $e;
 240                  } else {
 241                      $children_elements[ $e->$parent_field ][] = $e;
 242                  }
 243              }
 244          }
 245  
 246          foreach ( $top_level_elements as $e ) {
 247              $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
 248          }
 249  
 250          /*
 251           * If we are displaying all levels, and remaining children_elements is not empty,
 252           * then we got orphans, which should be displayed regardless.
 253           */
 254          if ( ( 0 === $max_depth ) && count( $children_elements ) > 0 ) {
 255              $empty_array = array();
 256              foreach ( $children_elements as $orphans ) {
 257                  foreach ( $orphans as $op ) {
 258                      $this->display_element( $op, $empty_array, 1, 0, $args, $output );
 259                  }
 260              }
 261          }
 262  
 263          return $output;
 264      }
 265  
 266      /**
 267       * Produces a page of nested elements.
 268       *
 269       * Given an array of hierarchical elements, the maximum depth, a specific page number,
 270       * and number of elements per page, this function first determines all top level root elements
 271       * belonging to that page, then lists them and all of their children in hierarchical order.
 272       *
 273       * $max_depth = 0 means display all levels.
 274       * $max_depth > 0 specifies the number of display levels.
 275       *
 276       * @since 2.7.0
 277       * @since 5.3.0 Formalized the existing `...$args` parameter by adding it
 278       *              to the function signature.
 279       *
 280       * @param array $elements  An array of elements.
 281       * @param int   $max_depth The maximum hierarchical depth.
 282       * @param int   $page_num  The specific page number, beginning with 1.
 283       * @param int   $per_page  Number of elements per page.
 284       * @param mixed ...$args   Optional additional arguments.
 285       * @return string XHTML of the specified page of elements.
 286       */
 287  	public function paged_walk( $elements, $max_depth, $page_num, $per_page, ...$args ) {
 288          if ( empty( $elements ) || $max_depth < -1 ) {
 289              return '';
 290          }
 291  
 292          $output = '';
 293  
 294          $parent_field = $this->db_fields['parent'];
 295  
 296          $count = -1;
 297          if ( -1 === $max_depth ) {
 298              $total_top = count( $elements );
 299          }
 300          if ( $page_num < 1 || $per_page < 0 ) {
 301              // No paging.
 302              $paging = false;
 303              $start  = 0;
 304              if ( -1 === $max_depth ) {
 305                  $end = $total_top;
 306              }
 307              $this->max_pages = 1;
 308          } else {
 309              $paging = true;
 310              $start  = ( (int) $page_num - 1 ) * (int) $per_page;
 311              $end    = $start + $per_page;
 312              if ( -1 === $max_depth ) {
 313                  $this->max_pages = (int) ceil( $total_top / $per_page );
 314              }
 315          }
 316  
 317          // Flat display.
 318          if ( -1 === $max_depth ) {
 319              if ( ! empty( $args[0]['reverse_top_level'] ) ) {
 320                  $elements = array_reverse( $elements );
 321                  $oldstart = $start;
 322                  $start    = $total_top - $end;
 323                  $end      = $total_top - $oldstart;
 324              }
 325  
 326              $empty_array = array();
 327              foreach ( $elements as $e ) {
 328                  ++$count;
 329                  if ( $count < $start ) {
 330                      continue;
 331                  }
 332                  if ( $count >= $end ) {
 333                      break;
 334                  }
 335                  $this->display_element( $e, $empty_array, 1, 0, $args, $output );
 336              }
 337              return $output;
 338          }
 339  
 340          /*
 341           * Separate elements into two buckets: top level and children elements.
 342           * Children_elements is two dimensional array, e.g.
 343           * $children_elements[10][] contains all sub-elements whose parent is 10.
 344           */
 345          $top_level_elements = array();
 346          $children_elements  = array();
 347          foreach ( $elements as $e ) {
 348              if ( empty( $e->$parent_field ) ) {
 349                  $top_level_elements[] = $e;
 350              } else {
 351                  $children_elements[ $e->$parent_field ][] = $e;
 352              }
 353          }
 354  
 355          $total_top = count( $top_level_elements );
 356          if ( $paging ) {
 357              $this->max_pages = (int) ceil( $total_top / $per_page );
 358          } else {
 359              $end = $total_top;
 360          }
 361  
 362          if ( ! empty( $args[0]['reverse_top_level'] ) ) {
 363              $top_level_elements = array_reverse( $top_level_elements );
 364              $oldstart           = $start;
 365              $start              = $total_top - $end;
 366              $end                = $total_top - $oldstart;
 367          }
 368          if ( ! empty( $args[0]['reverse_children'] ) ) {
 369              foreach ( $children_elements as $parent => $children ) {
 370                  $children_elements[ $parent ] = array_reverse( $children );
 371              }
 372          }
 373  
 374          foreach ( $top_level_elements as $e ) {
 375              ++$count;
 376  
 377              // For the last page, need to unset earlier children in order to keep track of orphans.
 378              if ( $end >= $total_top && $count < $start ) {
 379                      $this->unset_children( $e, $children_elements );
 380              }
 381  
 382              if ( $count < $start ) {
 383                  continue;
 384              }
 385  
 386              if ( $count >= $end ) {
 387                  break;
 388              }
 389  
 390              $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
 391          }
 392  
 393          if ( $end >= $total_top && count( $children_elements ) > 0 ) {
 394              $empty_array = array();
 395              foreach ( $children_elements as $orphans ) {
 396                  foreach ( $orphans as $op ) {
 397                      $this->display_element( $op, $empty_array, 1, 0, $args, $output );
 398                  }
 399              }
 400          }
 401  
 402          return $output;
 403      }
 404  
 405      /**
 406       * Calculates the total number of root elements.
 407       *
 408       * @since 2.7.0
 409       *
 410       * @param array $elements Elements to list.
 411       * @return int Number of root elements.
 412       */
 413  	public function get_number_of_root_elements( $elements ) {
 414          $num          = 0;
 415          $parent_field = $this->db_fields['parent'];
 416  
 417          foreach ( $elements as $e ) {
 418              if ( empty( $e->$parent_field ) ) {
 419                  ++$num;
 420              }
 421          }
 422          return $num;
 423      }
 424  
 425      /**
 426       * Unsets all the children for a given top level element.
 427       *
 428       * @since 2.7.0
 429       *
 430       * @param object $element           The top level element.
 431       * @param array  $children_elements The children elements.
 432       */
 433  	public function unset_children( $element, &$children_elements ) {
 434          if ( ! $element || ! $children_elements ) {
 435              return;
 436          }
 437  
 438          $id_field = $this->db_fields['id'];
 439          $id       = $element->$id_field;
 440  
 441          if ( ! empty( $children_elements[ $id ] ) && is_array( $children_elements[ $id ] ) ) {
 442              foreach ( (array) $children_elements[ $id ] as $child ) {
 443                  $this->unset_children( $child, $children_elements );
 444              }
 445          }
 446  
 447          unset( $children_elements[ $id ] );
 448      }
 449  }


Generated : Thu Apr 25 08:20:02 2024 Cross-referenced by PHPXref