home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-wp-walker.php < prev    next >
Encoding:
PHP Script  |  2017-10-02  |  12.1 KB  |  426 lines

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