Index: wp-includes/post-template.php =================================================================== --- wp-includes/post-template.php (revision 11807) +++ wp-includes/post-template.php (working copy) @@ -702,6 +702,8 @@ ); $r = wp_parse_args( $args, $defaults ); + if ( 'menu_order, post_title' == $r['sort_column'] && -1 != $r['depth'] ) + $r['sort_column'] = 'lft'; extract( $r, EXTR_SKIP ); $pages = get_pages($r); @@ -713,7 +715,10 @@ $output .= "\t"; if ( $show_option_none ) $output .= "\t\n"; - $output .= walk_page_dropdown_tree($pages, $depth, $r); + if ( 'lft' == $sort_solumn ) + $output .= walk_page_dropdown_tree($pages, $depth, $r, true); + else + $output .= walk_page_dropdown_tree($pages, $depth, $r, false); $output .= "\n"; } @@ -737,13 +742,15 @@ $defaults = array( 'depth' => 0, 'show_date' => '', 'date_format' => get_option('date_format'), - 'child_of' => 0, 'exclude' => '', + 'child_of' => 0, 'ancestor_of' => 0, 'exclude' => '', 'title_li' => __('Pages'), 'echo' => 1, 'authors' => '', 'sort_column' => 'menu_order, post_title', 'link_before' => '', 'link_after' => '' ); $r = wp_parse_args( $args, $defaults ); + if ( 'menu_order, post_title' == $r['sort_column'] && -1 != $r['depth'] ) + $r['sort_column'] = 'lft'; extract( $r, EXTR_SKIP ); $output = ''; @@ -766,7 +773,10 @@ global $wp_query; if ( is_page() || is_attachment() || $wp_query->is_posts_page ) $current_page = $wp_query->get_queried_object_id(); - $output .= walk_page_tree($pages, $r['depth'], $current_page, $r); + if ( 'lft' == $r['sort_column'] ) + $output .= walk_page_tree($pages, $r['depth'], $current_page, $r, true); + else + $output .= walk_page_tree($pages, $r['depth'], $current_page, $r, false); if ( $r['title_li'] ) $output .= ''; @@ -806,7 +816,7 @@ * @param array|string $args */ function wp_page_menu( $args = array() ) { - $defaults = array('sort_column' => 'menu_order, post_title', 'menu_class' => 'menu', 'echo' => true, 'link_before' => '', 'link_after' => ''); + $defaults = array('sort_column' => 'lft', 'menu_class' => 'menu', 'echo' => true, 'link_before' => '', 'link_after' => ''); $args = wp_parse_args( $args, $defaults ); $args = apply_filters( 'wp_page_menu_args', $args ); @@ -861,9 +871,13 @@ * @since 2.1.0 * @see Walker_Page::walk() for parameters and return description. */ -function walk_page_tree($pages, $depth, $current_page, $r) { - if ( empty($r['walker']) ) - $walker = new Walker_Page; +function walk_page_tree($pages, $depth, $current_page, $r, $mptt = false) { + if ( empty($r['walker']) ) { + if ( $mptt ) + $walker = new MPTT_Walker_Page; + else + $walker = new Walker_Page; + } else $walker = $r['walker']; @@ -880,8 +894,12 @@ */ function walk_page_dropdown_tree() { $args = func_get_args(); - if ( empty($args[2]['walker']) ) // the user's options are the third parameter - $walker = new Walker_PageDropdown; + if ( empty($args[2]['walker']) ) { // the user's options are the third parameter + if ( $args[3] ) + $walker = new MPTT_Walker_PageDropdown; + else + $walker = new Walker_PageDropdown; + } else $walker = $args[2]['walker']; Index: wp-includes/category.php =================================================================== --- wp-includes/category.php (revision 11807) +++ wp-includes/category.php (working copy) @@ -37,7 +37,7 @@ * @return array List of categories. */ function &get_categories( $args = '' ) { - $defaults = array( 'type' => 'category' ); + $defaults = array( 'type' => 'category', 'page' => 0, 'per_page' => 20 ); $args = wp_parse_args( $args, $defaults ); $taxonomy = apply_filters( 'get_categories_taxonomy', 'category', $args ); Index: wp-includes/taxonomy.php =================================================================== --- wp-includes/taxonomy.php (revision 11807) +++ wp-includes/taxonomy.php (working copy) @@ -434,7 +434,7 @@ $term_id = intval( $term_id ); - $terms = _get_term_hierarchy($taxonomy); + $terms = _get_term_hierarchy($taxonomy, 0, $term_id); if ( ! isset($terms[$term_id]) ) return array(); @@ -449,6 +449,7 @@ return $children; } + /** * Get sanitized Term field. * @@ -624,8 +625,9 @@ $defaults = array('orderby' => 'name', 'order' => 'ASC', 'hide_empty' => true, 'exclude' => '', 'exclude_tree' => '', 'include' => '', 'number' => '', 'fields' => 'all', 'slug' => '', 'parent' => '', - 'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '', - 'pad_counts' => false, 'offset' => '', 'search' => ''); + 'hierarchical' => true, 'child_of' => 0, 'ancestor_of' => 0, 'get' => '', + 'name__like' => '', 'pad_counts' => false, 'offset' => '', 'search' => '', + 'page' => 0, 'per_page' => 20); $args = wp_parse_args( $args, $defaults ); $args['number'] = absint( $args['number'] ); $args['offset'] = absint( $args['offset'] ); @@ -642,20 +644,15 @@ $args['hierarchical'] = false; $args['pad_counts'] = false; } - extract($args, EXTR_SKIP); - if ( $child_of ) { - $hierarchy = _get_term_hierarchy($taxonomies[0]); - if ( !isset($hierarchy[$child_of]) ) - return $empty_array; + if ( 0 < $args['page'] ) { + if ( 0 >= $args['per_page'] ) + $args['per_page'] = 20; + $args['number'] = $args['per_page']; + $args['offset'] = ($args['page'] - 1) * $args['per_page']; } + extract($args, EXTR_SKIP); - if ( $parent ) { - $hierarchy = _get_term_hierarchy($taxonomies[0]); - if ( !isset($hierarchy[$parent]) ) - return $empty_array; - } - // $args can be whatever, only use the args defined in defaults to compute the key $filter_key = ( has_filter('list_terms_exclusions') ) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : ''; $key = md5( serialize( compact(array_keys($defaults)) ) . serialize( $taxonomies ) . $filter_key ); @@ -680,6 +677,10 @@ $orderby = 't.slug'; else if ( 'term_group' == $_orderby ) $orderby = 't.term_group'; + else if ( 'lft' == $_orderby ) + $orderby = 'tt.lft'; + else if ( 'rgt' == $_orderby ) + $orderby = 'tt.rgt'; elseif ( empty($_orderby) || 'id' == $_orderby ) $orderby = 't.term_id'; @@ -754,7 +755,7 @@ $where .= ' AND tt.count > 0'; // don't limit the query results when we have to descend the family tree - if ( ! empty($number) && ! $hierarchical && empty( $child_of ) && '' === $parent ) { + if ( ! empty($number) && ( 'tt.lft' == $orderby || 'tt.rgt' == $orderby || ( ! $hierarchical && empty( $child_of ) && '' === $parent ) ) ) { if( $offset ) $limit = 'LIMIT ' . $offset . ',' . $number; else @@ -768,6 +769,22 @@ $where .= " AND (t.name LIKE '%$search%')"; } + if ( $child_of > 0 ) { + $parent_bounds = $wpdb->get_row( "SELECT lft, rgt FROM $wpdb->term_taxonomy WHERE taxonomy IN ($in_taxonomies) AND term_id = $child_of LIMIT 1" ); + $lft = $parent_bounds->lft; + $rgt = $parent_bounds->rgt; + $bounds = "AND tt.lft > $lft AND tt.rgt < $rgt "; + } else { + $bounds = ""; + } + + if ( $ancestor_of > 0 ) { + $child_bounds = $wpdb->get_row( "SELECT lft, rgt FROM $wpdb->term_taxonomy WHERE taxonomy IN ($in_taxonomies) AND term_id = $ancestor_of LIMIT 1" ); + $lft = $child_bounds->lft; + $rgt = $child_bounds->rgt; + $bounds .= "AND tt.lft <= $lft AND tt.rgt >= $rgt "; + } + $selects = array(); if ( 'all' == $fields ) $selects = array('t.*', 'tt.*'); @@ -777,7 +794,7 @@ $selects = array('t.term_id', 'tt.parent', 'tt.count', 't.name'); $select_this = implode(', ', apply_filters( 'get_terms_fields', $selects, $args )); - $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ($in_taxonomies) $where ORDER BY $orderby $order $limit"; + $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ($in_taxonomies) $where $bounds ORDER BY $orderby $order $limit"; $terms = $wpdb->get_results($query); if ( 'all' == $fields ) { @@ -790,12 +807,6 @@ return $terms; } - if ( $child_of ) { - $children = _get_term_hierarchy($taxonomies[0]); - if ( ! empty($children) ) - $terms = & _get_term_children($child_of, $terms, $taxonomies[0]); - } - // Update term counts to include children. if ( $pad_counts && 'all' == $fields ) _pad_term_counts($terms, $taxonomies[0]); @@ -1099,6 +1110,7 @@ * @since 2.3.0 * * @uses $wpdb + * @uses MPTT * @uses do_action() Calls both 'delete_term' and 'delete_$taxonomy' action * hooks, passing term object, term id. 'delete_term' gets an additional * parameter with the $taxonomy parameter. @@ -1155,6 +1167,11 @@ wp_set_object_terms($object, $terms, $taxonomy); } + if ( 'category' == $taxonomy ) { + $mptt = new MPTT_Category; + $mptt->remove_node( $term ); + } + $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $tt_id ) ); // Delete the term if no taxonomies use it. @@ -1330,6 +1347,7 @@ * @subpackage Taxonomy * @since 2.3.0 * @uses $wpdb + * @uses MPTT * * @uses do_action() Calls 'create_term' hook with the term id and taxonomy id as parameters. * @uses do_action() Calls 'create_$taxonomy' hook with term id and taxonomy id as parameters. @@ -1354,7 +1372,7 @@ if ( '' == trim($term) ) return new WP_Error('empty_term_name', __('A name is required for this term')); - $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => ''); + $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '', 'lft' => 0, 'rgt' => 0 ); $args = wp_parse_args($args, $defaults); $args['name'] = $term; $args['taxonomy'] = $taxonomy; @@ -1404,9 +1422,14 @@ if ( !empty($tt_id) ) return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id); - $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent') + array( 'count' => 0 ) ); + $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ) + array( 'count' => 0 ) ); $tt_id = (int) $wpdb->insert_id; + if ( 'category' == $taxonomy ) { + $mptt = new MPTT_Category; + $mptt->insert_node( $term_id ); + } + do_action("create_term", $term_id, $tt_id); do_action("create_$taxonomy", $term_id, $tt_id); @@ -1591,6 +1614,7 @@ * @since 2.3.0 * * @uses $wpdb + * @uses MPTT * @uses do_action() Will call both 'edit_term' and 'edit_$taxonomy' twice. * @uses apply_filters() Will call the 'term_id_filter' filter and pass the term * id and taxonomy id. @@ -1609,18 +1633,18 @@ $term_id = (int) $term_id; // First, get all of the original args - $term = get_term ($term_id, $taxonomy, ARRAY_A); + $old_term = get_term ($term_id, $taxonomy, ARRAY_A); - if ( is_wp_error( $term ) ) - return $term; + if ( is_wp_error( $old_term ) ) + return $old_term; // Escape data pulled from DB. - $term = add_magic_quotes($term); + $term = add_magic_quotes($old_term); // Merge old and new args with new args overwriting old ones. $args = array_merge($term, $args); - $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => ''); + $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '' ); $args = wp_parse_args($args, $defaults); $args = sanitize_term($args, $taxonomy, 'db'); extract($args, EXTR_SKIP); @@ -1668,10 +1692,18 @@ $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) ); } - $tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id) ); + $tt_id = $old_term['term_taxonomy_id']; + $tt_parent = $old_term['parent']; + $lft = $old_term['lft']; + $rgt = $old_term['rgt']; $wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) ); + if ( 'category' == $taxonomy && ( $tt_parent != $parent || $old_term['name'] != $name ) ) { + $mptt = new MPTT_Category; + $mptt->move_subtree( $term_id ); + } + do_action("edit_term", $term_id, $tt_id); do_action("edit_$taxonomy", $term_id, $tt_id); @@ -1989,26 +2021,37 @@ * @since 2.3.0 * * @uses update_option() Stores all of the children in "$taxonomy_children" - * option. That is the name of the taxonomy, immediately followed by '_children'. + * option if the full hierarchy is retrieved. That is the name of the taxonomy, + * immediately followed by '_children'. * * @param string $taxonomy Taxonomy Name * @return array Empty if $taxonomy isn't hierarachical or returns children as Term IDs. */ -function _get_term_hierarchy($taxonomy) { +function _get_term_hierarchy($taxonomy, $terms = 0, $root = 0) { if ( !is_taxonomy_hierarchical($taxonomy) ) return array(); - $children = get_option("{$taxonomy}_children"); - if ( is_array($children) ) - return $children; - $children = array(); - $terms = get_terms($taxonomy, 'get=all'); + if ( empty( $terms ) ) { + $cache = true; + $children = get_option("{$taxonomy}_children"); + if ( is_array($children) ) + return $children; + + $children = array(); + if ( $root > 0 ) + $terms = get_terms( $taxonomy, array( "child_of" => $root ) ); + else + $terms = get_terms($taxonomy, 'get=all'); + } + foreach ( $terms as $term ) { if ( $term->parent > 0 ) $children[$term->parent][] = $term->term_id; } - update_option("{$taxonomy}_children", $children); + if ($cache && ! $root) + update_option("{$taxonomy}_children", $children); + return $children; } Index: wp-includes/post.php =================================================================== --- wp-includes/post.php (revision 11807) +++ wp-includes/post.php (working copy) @@ -1172,6 +1172,9 @@ delete_option('page_for_posts'); } + $mptt = new MPTT_Page; + $mptt->remove_node( $postid ); + // Point children of this page to its parent, also clean the cache of affected children $children_query = $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type='page'", $postid); $children = $wpdb->get_results($children_query); @@ -1627,6 +1630,14 @@ $where = array( 'ID' => $post_ID ); } + if ( $post_type == 'page' ) { + $mptt = new MPTT_Page; + if ( $update ) + $mptt->move_subtree( $post_ID ); + else + $mptt->insert_node( $post_ID ); + } + if ( empty($data['post_name']) && !in_array( $data['post_status'], array( 'draft', 'pending' ) ) ) { $data['post_name'] = sanitize_title($data['post_title'], $post_ID); $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where ); @@ -2246,13 +2257,8 @@ */ function get_page_hierarchy($posts, $parent = 0) { $result = array ( ); - if ($posts) { foreach ( (array) $posts as $post) { - if ($post->post_parent == $parent) { - $result[$post->ID] = $post->post_name; - $children = get_page_hierarchy($posts, $post->ID); - $result += $children; //append $children to $result - } - } } + if ($posts) + usort( $posts, array( 'MPTT_Page', 'comparator' ) ); return $result; } @@ -2268,16 +2274,17 @@ */ function get_page_uri($page_id) { $page = get_page($page_id); - $uri = $page->post_name; + $uri = ''; // A page cannot be it's own parent. if ( $page->post_parent == $page->ID ) return $uri; - while ($page->post_parent != 0) { - $page = get_page($page->post_parent); - $uri = $page->post_name . "/" . $uri; - } + $mptt = new MPTT_Page; + $ancestors = $mptt->get_ancestors( $page_id ); + foreach ( $ancestors as $ancestor ) + $uri .= $ancestor->post_name; + $uri .= $page->post_name; return $uri; } @@ -2299,18 +2306,25 @@ global $wpdb; $defaults = array( - 'child_of' => 0, 'sort_order' => 'ASC', + 'child_of' => 0, 'ancestor_of' => 0, 'sort_order' => 'ASC', 'sort_column' => 'post_title', 'hierarchical' => 1, 'exclude' => '', 'include' => '', 'meta_key' => '', 'meta_value' => '', 'authors' => '', 'parent' => -1, 'exclude_tree' => '', - 'number' => '', 'offset' => 0 + 'number' => '', 'offset' => 0, 'page' => 0, 'per_page' => 20 ); $r = wp_parse_args( $args, $defaults ); extract( $r, EXTR_SKIP ); $number = (int) $number; $offset = (int) $offset; + if ( 0 < $page && $sort_column == 'lft' ) { + if ( 0 < $per_page ) + $number = $per_page; + else + $number = 20; + $offset = ($page - 1) * $number; + } $cache = array(); $key = md5( serialize( compact(array_keys($defaults)) ) ); @@ -2404,7 +2418,23 @@ if ( $parent >= 0 ) $where .= $wpdb->prepare(' AND post_parent = %d ', $parent); - $query = "SELECT * FROM $wpdb->posts $join WHERE (post_type = 'page' AND post_status = 'publish') $where "; + if ( $child_of > 0 ) { + $parent_bounds = $wpdb->get_row( "SELECT lft, rgt FROM $wpdb->posts WHERE ID = $child_of LIMIT 1" ); + $lft = $parent_bounds->lft; + $rgt = $parent_bounds->rgt; + $bounds = "AND lft > $lft AND rgt < $rgt "; + } else { + $bounds = ""; + } + + if ( $ancestor_of > 0 ) { + $child_bounds = $wpdb->get_row( "SELECT lft, rgt FROM $wpdb->posts WHERE ID = $ancestor_of LIMIT 1" ); + $lft = $child_bounds->lft; + $rgt = $child_bounds->rgt; + $bounds .= "AND lft <= $lft AND rgt >= $rgt "; + } + + $query = "SELECT * FROM $wpdb->posts $join WHERE (post_type = 'page' AND post_status = 'publish') $where $bounds"; $query .= $author_query; $query .= " ORDER BY " . $sort_column . " " . $sort_order ; @@ -2421,9 +2451,6 @@ // Update cache. update_page_cache($pages); - if ( $child_of || $hierarchical ) - $pages = & get_page_children($child_of, $pages); - if ( !empty($exclude_tree) ) { $exclude = array(); Index: wp-includes/version.php =================================================================== --- wp-includes/version.php (revision 11807) +++ wp-includes/version.php (working copy) @@ -15,7 +15,7 @@ * * @global int $wp_db_version */ -$wp_db_version = 11548; +$wp_db_version = 20001; // uber-high dummy value /** * Holds the TinyMCE version Index: wp-includes/mptt.php =================================================================== --- wp-includes/mptt.php (revision 0) +++ wp-includes/mptt.php (revision 0) @@ -0,0 +1,1044 @@ +table; + $table = $wpdb->$table_name; + $parent_field = $this->parent_field; + + wp_cache_flush (); + + $node = $this->get_node( $id ); + if ( ! $node ) + return false; + $parent_id = $node->$parent_field; + $parent = $this->get_node( $parent_id ); + + $lft = $this->get_lft( $id ); + $rgt = $lft + 1; + if ( isset( $parent->depth ) ) + $depth = $parent->depth + 1; + else + $depth = 0; + + if ( false === $wpdb->query( "UPDATE $table SET lft = lft + 2 WHERE lft >= $lft" ) ) + return false; + if ( false === $wpdb->query( "UPDATE $table SET rgt = rgt + 2 WHERE rgt >= $lft" ) ) + return false; + if ( false === $wpdb->query( "UPDATE $table SET lft = $lft, rgt = $rgt, depth = $depth WHERE $this->id_field = $id" ) ) + return false; + + wp_cache_flush (); + + return true; + } + + /** + * Removes a node from the tree + * + * @since MPTT-REPLACE + * @param int $id ID of node to remove from the structure + * @return bool True on success, false on failure + */ + function remove_node( $id ) { + global $wpdb; + $table = $this->table; + $node = $this->get_node( $id ); + if ( ! $node ) + return false; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET lft = lft - 1, rgt = rgt - 1, depth = depth - 1 WHERE lft > $node->lft AND rgt < $node->rgt" ) ) + return false; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET lft = lft - 2 WHERE lft > $node->rgt" ) ) + return false; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET rgt = rgt - 2 WHERE rgt > $node->rgt" ) ) + return false; + return true; + } + + /** + * Moves an existing node and its children to a new location in the tree + * + * @since MPTT-REPLACE + * @param int $id ID of node to move in the structure + * @return bool True on success, false on failure + */ + function move_subtree( $id ) { + global $wpdb; + $table = $this->table; + + $node = $this->get_node( $id ); + if ( ! $node ) + return false; + + $old_lft = $node->lft; + $old_rgt = $node->rgt; + $width = $old_rgt - $old_lft; + + $max = $wpdb->get_var( "SELECT MAX(rgt) FROM $wpdb->$this->table" ); + if ( false === $max ) + return false; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET lft = lft + $max, rgt = rgt + $max WHERE lft >= $old_lft AND rgt <= $old_rgt" ) ) + return false; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET lft = lft - $width WHERE lft > $old_rgt AND lft <= $max" ) ) + return false; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET rgt = rgt - $width WHERE rgt > $old_rgt AND rgt <= $max" ) ) + return false; + + $new_lft = $this->get_lft( $id ); + + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET lft = lft + $width WHERE lft >= $new_lft AND lft <= $max" ) ) + return false; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET rgt = rgt + $width WHERE rgt >= $new_lft AND rgt <= $max" ) ) + return false; + + $delta = $new_lft - $old_lft - $max; + if ( false === $wpdb->query( "UPDATE $wpdb->$table SET lft = lft + $delta, rgt = rgt + $delta WHERE lft > $max" ) ) + return false; + + return true; + } + + /** + * Comparator between two nodes for use with usort() + * + * @since MPTT-REPLACE + * @param object $a First node + * @param object $b Second node + * @return int Comparison between $a and $b + * @abstract + */ + function comparator( $a, $b ) { + return ( $a->lft - $b->lft ); + } + + /** + * Retrieves node as object + * + * @since MPTT-REPLACE + * @param int $id ID of node to retrieve from the structure + * @return object Returns node + * @abstract + */ + function get_node( $id ) {} + + /** + * Retrieves children of a given parent + * + * @since MPTT-REPLACE + * @param object $element Node whose siblings to retrieve + * @return array Returns array of node objects + * @abstract + */ + function get_siblings( $element ) {} + + /** + * Retrieves ancestors of a given node + * + * @since MPTT-REPLACE + * @param int $id ID of node whose ancestors to retrieve + * @param bool $self Determines whether to include the specified node or not. + * @return array Returns array of node objects + * @abstract + */ + function get_ancestors( $id, $self ) {} + + /** + * Retrieves proper location for a node + * + * @since MPTT-REPLACE + * @param int $id ID of node to locate in the structure + * @return int lft value for node's new location + * @abstract + */ + function get_lft( $id ) {} + +} + +/** + * Manage MPTT data for categories + * + * @package WordPress + * @since MPTT-REPLACE + * @uses MPTT + */ +class MPTT_Category extends MPTT { + + /** + * Table in the database + * + * @since MPTT-REPLACE + * @var string + * @access private + */ + var $table = 'term_taxonomy'; + + /** + * ID field in the database. + * + * @since MPTT-REPLACE + * @var string + * @access private + */ + var $id_field = 'term_id'; + + /** + * Parent field in the database + * + * @since MPTT-REPLACE + * @var string + * @access private + */ + var $parent_field = 'parent'; + + /** + * Retrieves node as object + * + * @since MPTT-REPLACE + * @param int $id ID of node to retrieve from the structure + * @return object Returns node + */ + function get_node( $id ) { + return get_term( $id, 'category' ); + } + + /** + * Retrieves the siblings of a given element + * + * @since MPTT-REPLACE + * @param object $element Element whose siblings to retrieve + * @return array Returns array of node objects + */ + function get_siblings( $element ) { + $args = array( 'parent' => $element->parent, 'exclude' => $element->term_id, 'hide_empty' => false ); + $siblings = array(); + $siblings = get_terms( array( 'category' ), $args ); + return $siblings; + } + + /** + * Retrieves ancestors of a given node + * + * @since MPTT-REPLACE + * @param int $id ID of node whose ancestors to retrieve + * @param bool $self Determines whether to include the specified node or not. + * @return array Returns array of node objects + */ + function get_ancestors( $id, $self = false ) { + $args = array( 'ancestor_of' => $id, 'orderby' => 'lft', 'hide_empty' => false ); + if ( !$self ) + $args['exclude'] = $id; + return get_terms( array( 'category' ), $args ); + } + + /** + * Retrieves proper location for a node + * + * @since MPTT-REPLACE + * @param int $id ID of node to locate in the structure + * @return int lft value for node's new location + */ + function get_lft( $id ) { + $node = $this->get_node( $id ); + $siblings = $this->get_siblings( $node ); + if ( empty( $siblings ) ) { + if ( $node->parent ) { + $parent = $this->get_node( $node->parent ); + return $parent->rgt; + } else { + return 1; + } + } + $index = 0; + $total = count( $siblings ); + // Order by name. + while ( $index < $total && strcmp( $siblings[$index]->name, $node->name ) <= 0 ) + $index++; + if ( $total <= $index ) + $index = $total - 1; + if ( strcmp( $siblings[$index]->name, $node->name ) > 0 ) + return $siblings[$index]->lft; + else + return ( $siblings[$index]->rgt + 1 ); + } + +} + +/** + * Manage MPTT data for pages + * + * @package WordPress + * @since MPTT-REPLACE + * @uses MPTT + */ +class MPTT_Page extends MPTT { + + /** + * Table in the database + * + * @since MPTT-REPLACE + * @var string + * @access private + */ + var $table = 'posts'; + + /** + * ID field in the database. + * + * @since MPTT-REPLACE + * @var string + * @access private + */ + var $id_field = 'ID'; + + /** + * Parent field in the database + * + * @since MPTT-REPLACE + * @var string + * @access private + */ + var $parent_field = 'post_parent'; + + /** + * Retrieves node as object + * + * @since MPTT-REPLACE + * @param int $id ID of node to retrieve from the structure + * @return object Returns node + */ + function get_node( $id ) { + return get_post( $id ); + } + + /** + * Retrieves the siblings of a given element + * + * @since MPTT-REPLACE + * @param object $element Element whose siblings to retrieve + * @return array Returns array of node objects + */ + function get_siblings( $element ) { + $args = array( 'parent' => $element->post_parent, 'exclude' => $element->ID ); + $siblings = array(); + $siblings = get_pages( $args ); + return $siblings; + } + + /** + * Retrieves ancestors of a given node + * + * @since MPTT-REPLACE + * @param int $id ID of node whose ancestors to retrieve + * @param bool $self Determines whether to include the specified node or not. + * @return array Returns array of node objects + */ + function get_ancestors( $id, $self = false ) { + $args = array( 'ancestor_of' => $id, 'sort_column' => 'lft' ); + if ( !$self ) + $args['exclude'] = $id; + return get_pages( $args ); + } + + /** + * Retrieves proper location for a node + * + * @since MPTT-REPLACE + * @param int $id ID of node to locate in the structure + * @return int lft value for node's new location + */ + function get_lft( $id ) { + $node = $this->get_node( $id ); + $siblings = $this->get_siblings( $node ); + if ( empty( $siblings ) ) { + if ( $node->post_parent ) { + $parent = $this->get_node( $node->post_parent ); + return $parent->rgt; + } else { + return 1; + } + } + + $index = 0; + $total = count( $siblings ); + // Order by order, then by name. + while ( $index < $total ) { + if ( $siblings[$index]->menu_order < $node->menu_order ) + $index++; + else if ( $siblings[$index]->menu_order == $node->menu_order && strcmp( $siblings[$index]->post_title, $node->post_title ) < 0 ) + $index++; + else + break; + } + if ( $total <= $index) + $index = $total - 1; + if ( $siblings[$index]->menu_order > $node->menu_order ) + return $siblings[$index]->lft; + elseif ( $siblings[$index]->menu_order == $node->menu_order && strcmp( $siblings[$index]->post_title, $node->post_title ) > 0 ) + return $siblings[$index]->lft; + else + return ( $siblings[$index]->rgt + 1 ); + } + +} + +/** + * A class for displaying various tree-like structures. + * + * Extend the MPTT_Walker class to use it, see examples at the below. Child classes + * do not need to implement all of the abstract methods in the class. The child + * only needs to implement the methods that are needed. Also, the methods are + * not strictly abstract in that the parameter definition needs to be followed. + * The child classes can have additional parameters. + * + * @package WordPress + * @since mptt-REPLACE + * @abstract + */ +class MPTT_Walker { + /** + * What the class handles. + * + * @since MPTT-REPLACE + * @var string + * @access public + */ + var $tree_type; + + /** + * DB fields to use. + * + * @since MPTT-REPLACE + * @var array + * @access protected + */ + var $db_fields; + + /** + * Max number of pages walked by the paged walker + * + * @since MPTT-REPLACE + * @var int + * @access protected + */ + var $max_pages = 1; + + /** + * Starts the list before the elements are added. + * + * Additional parameters are used in child classes. The args parameter holds + * additional values that may be used with the child class methods. This + * method is called at the start of the output list. + * + * @since MPTT-REPLACE + * @abstract + * + * @param string $output Passed by reference. Used to append additional content. + */ + function start_lvl(&$output) {} + + /** + * Ends the list of after the elements are added. + * + * Additional parameters are used in child classes. The args parameter holds + * additional values that may be used with the child class methods. This + * method finishes the list at the end of output of the elements. + * + * @since MPTT-REPLACE + * @abstract + * + * @param string $output Passed by reference. Used to append additional content. + */ + function end_lvl(&$output) {} + + /** + * Start the element output. + * + * Additional parameters are used in child classes. The args parameter holds + * additional values that may be used with the child class methods. Includes + * the element output also. + * + * @since MPTT-REPLACE + * @abstract + * + * @param string $output Passed by reference. Used to append additional content. + */ + function start_el(&$output) {} + + /** + * Ends the element output, if needed. + * + * Additional parameters are used in child classes. The args parameter holds + * additional values that may be used with the child class methods. + * + * @since MPTT-REPLACE + * @abstract + * + * @param string $output Passed by reference. Used to append additional content. + */ + function end_el(&$output) {} + + /** + * Display array of elements hierarchically. + * + * It is a generic function which assumes the input is ordered by lft value. + * max_depth = -1 means flatly display every element. max_depth = + * 0 means display all levels. max_depth > 0 specifies the number of + * display levels. + * + * @since MPTT-REPLACE + * + * @param array $elements + * @param int $max_depth + * @return string + */ + function walk( $elements, $max_depth) { + + $args = array_slice(func_get_args(), 2); + $output = ''; + + if ($max_depth < -1) //invalid parameter + return $output; + + if (empty($elements)) //nothing to walk + return $output; + + $id_field = $this->db_fields['id']; + $parent_field = $this->db_fields['parent']; + + $max_i = count( $elements ); + + if ( -1 == $max_depth ) { + for ( $i = 0; $i < $max_i; $i++ ) { + $cb_args = array_merge( array( &$output, $elements[$i], 0 ), $args ); + call_user_func_array( array( &$this, 'start_el' ), $cb_args ); + call_user_func_array( array( &$this, 'end_el' ), $cb_args ); + } + return $output; + } + + $stack = array(); + $elements[$max_i] = (object) array( 'depth' => $elements[0]->depth ); + for ( $i = 0; $i < $max_i; $i++ ) { + + $current = $elements[$i]; + $depth = $current->depth; + if ( $max_depth != 0 && $depth >= $max_depth ) + continue; + $next_depth = $elements[$i + 1]->depth; + + $cb_args = array_merge( array( &$output, $current, $depth ), $args ); + call_user_func_array( array( &$this, 'start_el' ), $cb_args ); + + if ( $depth == $next_depth ) + call_user_func_array( array( &$this, 'end_el' ), $cb_args ); + else if ( $depth < $next_depth ) { + $cb_args[2]++; + call_user_func_array( array( &$this, 'start_lvl' ), $cb_args ); + array_push( $stack, $i ); + } + else { + for ( $lvl = ($depth - $next_depth); $lvl > 0; $lvl-- ) { + $finish = array_pop( $stack ); + $cb_lvl_args = array_merge( array( &$output, &$elements[$finish], $next_depth + $lvl ), $args ); + call_user_func_array( array( &$this, 'end_lvl' ), $cb_args ); + $cb_args = array_merge( array( &$output, &$elements[$finish], $elements[$finish]->depth ), $args ); + call_user_func_array( array( &$this, 'end_el' ), $cb_args ); + } + } + + } + + return $output; + } + +} + +/** + * Create HTML list of categories. + * + * @package WordPress + * @since MPTT-REPLACE + * @uses MPTT_Walker + */ +class MPTT_Walker_Category extends MPTT_Walker { + /** + * @see MPTT_Walker::$tree_type + * @since MPTT-REPLACE + * @var string + */ + var $tree_type = 'category'; + + /** + * @see MPTT_Walker::$db_fields + * @since MPTT-REPLACE + * @todo Decouple this + * @var array + */ + var $db_fields = array ('parent' => 'parent', 'id' => 'term_id'); + + /** + * @see MPTT_Walker::start_lvl() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param object $element Not used. + * @param int $depth Depth of category. Used for tab indentation. + * @param array $args Will only append content if style argument value is 'list'. + */ + function start_lvl(&$output, $element, $depth, $args) { + if ( 'list' != $args['style'] ) + return; + + $indent = str_repeat("\t", $depth); + $output .= "$indent\n"; + } + + /** + * @see MPTT_Walker::start_el() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param object $category Category data object. + * @param int $depth Depth of category in reference to parents. + * @param array $args + */ + function start_el(&$output, $category, $depth, $args) { + extract($args); + + $cat_name = esc_attr( $category->name); + $cat_name = apply_filters( 'list_cats', $cat_name, $category ); + $link = 'description) ) + $link .= 'title="' . sprintf(__( 'View all posts filed under %s' ), $cat_name) . '"'; + else + $link .= 'title="' . esc_attr( strip_tags( apply_filters( 'category_description', $category->description, $category ) ) ) . '"'; + $link .= '>'; + $link .= $cat_name . ''; + + if ( (! empty($feed_image)) || (! empty($feed)) ) { + $link .= ' '; + + if ( empty($feed_image) ) + $link .= '('; + + $link .= ''; + $link .= ''; + if ( empty($feed_image) ) + $link .= ')'; + } + + if ( isset($show_count) && $show_count ) + $link .= ' (' . intval($category->count) . ')'; + + if ( isset($show_date) && $show_date ) { + $link .= ' ' . gmdate('Y-m-d', $category->last_update_timestamp); + } + + if ( isset($current_category) && $current_category ) + $_current_category = get_category( $current_category ); + + if ( 'list' == $args['style'] ) { + $output .= "\tterm_id; + if ( isset($current_category) && $current_category && ($category->term_id == $current_category) ) + $class .= ' current-cat'; + elseif ( isset($_current_category) && $_current_category && ($category->term_id == $_current_category->parent) ) + $class .= ' current-cat-parent'; + $output .= ' class="'.$class.'"'; + $output .= ">$link\n"; + } else { + $output .= "\t$link
\n"; + } + } + + /** + * @see MPTT_Walker::end_el() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param object $page Not used. + * @param int $depth Depth of category. Not used. + * @param array $args Only uses 'list' for whether should append to output. + */ + function end_el(&$output, $category, $depth, $args) { + if ( 'list' != $args['style'] ) + return; + + $output .= "\n"; + } + +} + +/** + * Create HTML dropdown list of Categories. + * + * @package WordPress + * @since MPTT-REPLACE + * @uses MPTT_Walker + */ +class MPTT_Walker_CategoryDropdown extends MPTT_Walker { + /** + * @see Walker::$tree_type + * @since MPTT-REPLACE + * @var string + */ + var $tree_type = 'category'; + + /** + * @see Walker::$db_fields + * @since MPTT-REPLACE + * @todo Decouple this + * @var array + */ + var $db_fields = array ('parent' => 'parent', 'id' => 'term_id'); + + /** + * @see Walker::start_el() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param object $category Category data object. + * @param int $depth Depth of category. Used for padding. + * @param array $args Uses 'selected', 'show_count', and 'show_last_update' keys, if they exist. + */ + function start_el(&$output, $category, $depth, $args) { + $pad = str_repeat(' ', $depth * 3); + + $cat_name = apply_filters('list_cats', $category->name, $category); + $output .= "\t\n"; + } +} + +/** + * Create HTML list of pages. + * + * @package WordPress + * @since MPTT-REPLACE + * @uses MPTT_Walker + */ +class MPTT_Walker_Page extends MPTT_Walker { + /** + * @see Walker::$tree_type + * @since MPTT-REPLACE + * @var string + */ + var $tree_type = 'page'; + + /** + * @see Walker::$db_fields + * @since MPTT-REPLACE + * @todo Decouple this. + * @var array + */ + var $db_fields = array ('parent' => 'post_parent', 'id' => 'ID'); + + /** + * @see Walker::start_lvl() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param int $depth Depth of page. Used for padding. + */ + function start_lvl(&$output, $element, $depth) { + $indent = str_repeat("\t", $depth); + $output .= "\n$indent\n"; + } + + /** + * @see Walker::start_el() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param object $page Page data object. + * @param int $depth Depth of page. Used for padding. + * @param int $current_page Page ID. + * @param array $args + */ + function start_el(&$output, $page, $depth, $args, $current_page) { + if ( $depth ) + $indent = str_repeat("\t", $depth); + else + $indent = ''; + + extract($args, EXTR_SKIP); + $css_class = array('page_item', 'page-item-'.$page->ID); + if ( !empty($current_page) ) { + $_current_page = get_page( $current_page ); + if ( isset($_current_page->ancestors) && in_array($page->ID, (array) $_current_page->ancestors) ) + $css_class[] = 'current_page_ancestor'; + if ( $page->ID == $current_page ) + $css_class[] = 'current_page_item'; + elseif ( $_current_page && $page->ID == $_current_page->post_parent ) + $css_class[] = 'current_page_parent'; + } elseif ( $page->ID == get_option('page_for_posts') ) { + $css_class[] = 'current_page_parent'; + } + + $css_class = implode(' ', apply_filters('page_css_class', $css_class, $page)); + + $output .= $indent . '
  • ' . $link_before . apply_filters('the_title', $page->post_title) . $link_after . ''; + + if ( !empty($show_date) ) { + if ( 'modified' == $show_date ) + $time = $page->post_modified; + else + $time = $page->post_date; + + $output .= " " . mysql2date($date_format, $time); + } + } + + /** + * @see Walker::end_el() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param object $page Page data object. Not used. + * @param int $depth Depth of page. Not Used. + */ + function end_el(&$output, $page, $depth) { + $output .= "
  • \n"; + } + +} + +/** + * Create HTML dropdown list of pages. + * + * @package WordPress + * @since MPTT-REPLACE + * @uses Walker + */ +class MPTT_Walker_PageDropdown extends MPTT_Walker { + /** + * @see Walker::$tree_type + * @since MPTT-REPLACE + * @var string + */ + var $tree_type = 'page'; + + /** + * @see Walker::$db_fields + * @since MPTT-REPLACE + * @todo Decouple this + * @var array + */ + var $db_fields = array ('parent' => 'post_parent', 'id' => 'ID'); + + /** + * @see Walker::start_el() + * @since MPTT-REPLACE + * + * @param string $output Passed by reference. Used to append additional content. + * @param object $page Page data object. + * @param int $depth Depth of page in reference to parent pages. Used for padding. + * @param array $args Uses 'selected' argument for selected page to set selected HTML attribute for option element. + */ + function start_el(&$output, $page, $depth, $args) { + $pad = str_repeat(' ', $depth * 3); + + $output .= "\t\n"; + } +} + +/** + * Build MPTT data for terms. + * + * @since MPTT-REPLACE + * @uses populate_mptt_taxonomy + */ +function build_mptt_categories() { + global $wp_taxonomies; + foreach ( $wp_taxonomies as $taxonomy ) { + if ( is_taxonomy_hierarchical( $taxonomy->name ) ) { + $current = 1; + $terms = get_terms( array( $taxonomy->name ), 'get=all,orderby=name' ); + $hierarchy = _get_term_hierarchy( $taxonomy->name ); + foreach( $terms as $term ) { + if ( $term->parent == 0 ) + populate_mptt_taxonomy( $term->term_id, $taxonomy->name, $current, $hierarchy, 0 ); + } + } + } +} + +/** + * Recursive helper for build_mptt_categories + * + * @since MPTT-REPLACE + * @param int $term_id ID of term to generate data for + * @param string $taxonomy Type of term + * @param int $current Counter for lft or rgt value to use + * @param array $hierarchy Hierarchy of terms + * @param int $depth Depth of term + */ +function populate_mptt_taxonomy( $term_id, $taxonomy, &$current, &$hierarchy, $depth ) { + global $wpdb; + if ( 0 >= $term_id ) + return; + $lft = $current++; + if ( isset( $hierarchy[$term_id] ) ) { + foreach ( $hierarchy[$term_id] as $child ) + populate_mptt_taxonomy( $child, $taxonomy, $current, $hierarchy, $depth + 1 ); + } + $rgt = $current++; + $wpdb->update( $wpdb->term_taxonomy, compact( 'lft', 'rgt', 'depth' ), compact( 'taxonomy', 'term_id' ) ); +} + +/** + * Build MPTT data for pages. + * + * @since MPTT-REPLACE + * @uses populate_mptt_page + */ +function build_mptt_pages() { + $current = 1; + $pages = get_pages( array( 'sort_column' => 'menu_order, post_title' ) ); + $hierarchy = array(); + foreach ( $pages as $page ) + $hierarchy[$page->post_parent][] = $page; + foreach( $hierarchy[0] as $page ) + populate_mptt_page( $page->ID, $current, $hierarchy, 0 ); +} + +/** + * Recursive helper build_mptt_pages + * + * @since MPTT-REPLACE + * @param int $page_id ID of page to generate data for + * @param int $current Counter for lft or rgt value to use + * @param array $hierarchy Hierarchy of pages + * @param int $depth Depth of term + */ +function populate_mptt_page( $page_id, &$current, &$hierarchy, $depth ) { + global $wpdb; + if ( 0 >= $page_id ) + return; + $lft = $current++; + if ( isset( $hierarchy[$page_id] ) && is_array( $hierarchy[$page_id] ) ) { + foreach ( $hierarchy[$page_id] as $child ) + populate_mptt_page( $child, $current, $hierarchy, $depth + 1 ); + } + $rgt = $current++; + $wpdb->update( $wpdb->posts, compact( 'lft', 'rgt', 'depth' ), array( 'ID' => $page_id ) ); +} + +?> Index: wp-includes/classes.php =================================================================== --- wp-includes/classes.php (revision 11807) +++ wp-includes/classes.php (working copy) @@ -1386,23 +1386,6 @@ $output .= "\t$link
    \n"; } } - - /** - * @see Walker::end_el() - * @since 2.1.0 - * - * @param string $output Passed by reference. Used to append additional content. - * @param object $page Not used. - * @param int $depth Depth of category. Not used. - * @param array $args Only uses 'list' for whether should append to output. - */ - function end_el(&$output, $page, $depth, $args) { - if ( 'list' != $args['style'] ) - return; - - $output .= "\n"; - } - } /** Index: wp-includes/category-template.php =================================================================== --- wp-includes/category-template.php (revision 11807) +++ wp-includes/category-template.php (working copy) @@ -345,8 +345,11 @@ $r = wp_parse_args( $args, $defaults ); $r['include_last_update_time'] = $r['show_last_update']; + if ( 'name' == $r['orderby'] && $r['hierarchical'] ) + $r['orderby'] = 'lft'; extract( $r ); + $tab_index_attribute = ''; if ( (int) $tab_index > 0 ) $tab_index_attribute = " tabindex=\"$tab_index\""; @@ -373,8 +376,10 @@ $depth = $r['depth']; // Walk the full depth. else $depth = -1; // Flat. - - $output .= walk_category_dropdown_tree( $categories, $depth, $r ); + if ( 'lft' == $orderby ) + $output .= walk_category_dropdown_tree( $categories, $depth, $r, true); + else + $output .= walk_category_dropdown_tree( $categories, $depth, $r, false ); $output .= "\n"; } @@ -444,6 +449,8 @@ if ( true == $r['hierarchical'] ) { $r['exclude_tree'] = $r['exclude']; $r['exclude'] = ''; + if ( 'name' == $r['orderby'] ) + $r['orderby'] = 'lft'; } extract( $r ); @@ -476,7 +483,10 @@ else $depth = -1; // Flat. - $output .= walk_category_tree( $categories, $depth, $r ); + if ( $orderby == 'lft' ) + $output .= walk_category_tree( $categories, $depth, $r, true ); + else + $output .= walk_category_tree( $categories, $depth, $r, false ); } if ( $title_li && 'list' == $style ) @@ -699,8 +709,14 @@ function walk_category_tree() { $args = func_get_args(); // the user's options are the third parameter - if ( empty($args[2]['walker']) || !is_a($args[2]['walker'], 'Walker') ) - $walker = new Walker_Category; + if ( empty($args[2]['walker']) || !is_a($args[2]['walker'], 'Walker') ) { + if ( true === $args[3] ) { + $walker = new MPTT_Walker_Category; + } + else { + $walker = new Walker_Category; + } + } else $walker = $args[2]['walker']; @@ -717,8 +733,12 @@ function walk_category_dropdown_tree() { $args = func_get_args(); // the user's options are the third parameter - if ( empty($args[2]['walker']) || !is_a($args[2]['walker'], 'Walker') ) - $walker = new Walker_CategoryDropdown; + if ( empty($args[2]['walker']) || !is_a($args[2]['walker'], 'Walker') ) { + if ( $args[3] ) + $walker = new MPTT_Walker_CategoryDropdown; + else + $walker = new Walker_CategoryDropdown; + } else $walker = $args[2]['walker']; Index: wp-settings.php =================================================================== --- wp-settings.php (revision 11807) +++ wp-settings.php (working copy) @@ -251,6 +251,7 @@ require (ABSPATH . WPINC . '/compat.php'); require (ABSPATH . WPINC . '/functions.php'); require (ABSPATH . WPINC . '/classes.php'); +require (ABSPATH . WPINC . '/mptt.php'); require_wp_db(); Index: wp-admin/includes/upgrade.php =================================================================== --- wp-admin/includes/upgrade.php (revision 11807) +++ wp-admin/includes/upgrade.php (working copy) @@ -345,6 +345,12 @@ if ( $wp_current_db_version < 10360 ) upgrade_280(); + if ( $wp_current_db_version < 20000 ) + build_mptt_categories(); + + if ( $wp_current_db_version < 20001 ) + build_mptt_pages(); + maybe_disable_automattic_widgets(); update_option( 'db_version', $wp_db_version ); Index: wp-admin/includes/schema.php =================================================================== --- wp-admin/includes/schema.php (revision 11807) +++ wp-admin/includes/schema.php (working copy) @@ -42,10 +42,15 @@ taxonomy varchar(32) NOT NULL default '', description longtext NOT NULL, parent bigint(20) unsigned NOT NULL default 0, + rgt bigint(20) NOT NULL default 0, + lft bigint(20) NOT NULL default 0, + depth bigint(20) NOT NULL default 0, count bigint(20) NOT NULL default 0, PRIMARY KEY (term_taxonomy_id), UNIQUE KEY term_id_taxonomy (term_id,taxonomy), - KEY taxonomy (taxonomy) + KEY taxonomy (taxonomy), + KEY lft (lft), + KEY rgt (rgt) ) $charset_collate; CREATE TABLE $wpdb->term_relationships ( object_id bigint(20) unsigned NOT NULL default 0, @@ -74,7 +79,7 @@ KEY comment_approved (comment_approved), KEY comment_post_ID (comment_post_ID), KEY comment_approved_date_gmt (comment_approved,comment_date_gmt), - KEY comment_date_gmt (comment_date_gmt) + KEY comment_date_gmt (comment_date_gmt), ) $charset_collate; CREATE TABLE $wpdb->links ( link_id bigint(20) unsigned NOT NULL auto_increment, @@ -135,10 +140,15 @@ post_type varchar(20) NOT NULL default 'post', post_mime_type varchar(100) NOT NULL default '', comment_count bigint(20) NOT NULL default '0', + lft bigint(20) NOT NULL default '0', + rgt bigint(20) NOT NULL default '0', + depth bigint(20) NOT NULL default '0', PRIMARY KEY (ID), KEY post_name (post_name), KEY type_status_date (post_type,post_status,post_date,ID), - KEY post_parent (post_parent) + KEY post_parent (post_parent), + KEY lft (lft), + KEY rgt (rgt) ) $charset_collate; CREATE TABLE $wpdb->users ( ID bigint(20) unsigned NOT NULL auto_increment, @@ -303,7 +313,7 @@ 'widget_categories' => array(), 'widget_text' => array(), 'widget_rss' => array(), - + // 2.8 'timezone_string' => '' ); Index: wp-admin/includes/template.php =================================================================== --- wp-admin/includes/template.php (revision 11807) +++ wp-admin/includes/template.php (working copy) @@ -9,6 +9,7 @@ */ // Ugly recursive category stuff. + /** * {@internal Missing Short Description}} * @@ -20,89 +21,32 @@ * @param unknown_type $page * @param unknown_type $per_page */ -function cat_rows( $parent = 0, $level = 0, $categories = 0, $page = 1, $per_page = 20 ) { +function cat_rows( $parent = 0, $level = 0, $categories = 0, $pagenum = 1, $per_page = 20 ) { - $count = 0; - - if ( empty($categories) ) { - + if ( empty( $categories ) ) { $args = array('hide_empty' => 0); if ( !empty($_GET['s']) ) $args['search'] = $_GET['s']; - + $args['page'] = $pagenum; + $args['per_page'] = $per_page; + $args['orderby'] = 'lft'; $categories = get_categories( $args ); - if ( empty($categories) ) return false; + } else { + usort( $categories, array( 'MPTT_Category', 'comparator' ) ); + $categories = array_slice( $categories, $per_page * ($pagenum - 1), $per_page ); } - $children = _get_term_hierarchy('category'); - - _cat_rows( $parent, $level, $categories, $children, $page, $per_page, $count ); - -} - -/** - * {@internal Missing Short Description}} - * - * @since unknown - * - * @param unknown_type $categories - * @param unknown_type $count - * @param unknown_type $parent - * @param unknown_type $level - * @param unknown_type $page - * @param unknown_type $per_page - * @return unknown - */ -function _cat_rows( $parent = 0, $level = 0, $categories, &$children, $page = 1, $per_page = 20, &$count ) { - - $start = ($page - 1) * $per_page; - $end = $start + $per_page; - ob_start(); - - foreach ( $categories as $key => $category ) { - if ( $count >= $end ) - break; - - if ( $category->parent != $parent && empty($_GET['s']) ) - continue; - - // If the page starts in a subtree, print the parents. - if ( $count == $start && $category->parent > 0 ) { - - $my_parents = array(); - $p = $category->parent; - while ( $p ) { - $my_parent = get_category( $p ); - $my_parents[] = $my_parent; - if ( $my_parent->parent == 0 ) - break; - $p = $my_parent->parent; - } - - $num_parents = count($my_parents); - while( $my_parent = array_pop($my_parents) ) { - echo "\t" . _cat_row( $my_parent, $level - $num_parents ); - $num_parents--; - } - } - - if ( $count >= $start ) - echo "\t" . _cat_row( $category, $level ); - - unset( $categories[ $key ] ); - - $count++; - - if ( isset($children[$category->term_id]) ) - _cat_rows( $category->term_id, $level + 1, $categories, $children, $page, $per_page, $count ); + if ( 0 != $categories[0]->depth ) { + $mptt = new MPTT_Category; + $ancestors = $mptt->get_ancestors( $categories[0]->term_id ); + $categories = array_merge( $ancestors, $categories ); } - $output = ob_get_contents(); - ob_end_clean(); + foreach ( $categories as $cat ) + echo "\t" . _cat_row( $cat, $cat->depth ); - echo $output; } /** @@ -1570,30 +1514,14 @@ * @param unknown_type $page * @param unknown_type $level */ -function display_page_row( $page, $level = 0 ) { +function display_page_row( $page ) { global $post; static $rowclass; + $level = $page->depth; $post = $page; setup_postdata($page); - if ( 0 == $level && (int)$page->post_parent > 0 ) { - //sent level 0 by accident, by default, or because we don't know the actual level - $find_main_page = (int)$page->post_parent; - while ( $find_main_page > 0 ) { - $parent = get_page($find_main_page); - - if ( is_null($parent) ) - break; - - $level++; - $find_main_page = (int)$parent->post_parent; - - if ( !isset($parent_name) ) - $parent_name = $parent->post_title; - } - } - $page->post_title = esc_html( $page->post_title ); $pad = str_repeat( '— ', $level ); $id = (int) $page->ID; @@ -1742,142 +1670,37 @@ * @param unknown_type $per_page * @return unknown */ -function page_rows($pages, $pagenum = 1, $per_page = 20) { - global $wpdb; - - $level = 0; - +function page_rows($pages = 0, $pagenum = 1, $per_page = 20) { + if ( 0 >= $pagenum ) + $pagenum = 1; + if ( 0 >= $per_page ) + $per_page = 20; + if ( ! $pages ) { - $pages = get_pages( array('sort_column' => 'menu_order') ); - - if ( ! $pages ) + $args = array('sort_column' => 'lft', 'page' => $pagenum, 'per_page' => $per_page); + $pages = get_pages( $args ); + if ( empty( $pages ) ) return false; + } else { + usort( $pages, array( 'MPTT_Page', 'comparator' ) ); + $pages = array_slice( $pages, $per_page * ($pagenum - 1), $per_page ); } - /* - * arrange pages into two parts: top level pages and children_pages - * children_pages is two dimensional array, eg. - * children_pages[10][] contains all sub-pages whose parent is 10. - * It only takes O(N) to arrange this and it takes O(1) for subsequent lookup operations - * If searching, ignore hierarchy and treat everything as top level - */ - if ( empty($_GET['s']) ) { - - $top_level_pages = array(); - $children_pages = array(); - - foreach ( $pages as $page ) { - - // catch and repair bad pages - if ( $page->post_parent == $page->ID ) { - $page->post_parent = 0; - $wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET post_parent = '0' WHERE ID = %d", $page->ID) ); - clean_page_cache( $page->ID ); - } - - if ( 0 == $page->post_parent ) - $top_level_pages[] = $page; - else - $children_pages[ $page->post_parent ][] = $page; - } - - $pages = &$top_level_pages; + if ( 0 != $pages[0]->depth ) { + $mptt = new MPTT_Page; + $ancestors = $mptt->get_ancestors( $pages[0]->ID ); + $pages = array_merge ($ancestors, $pages); } - $count = 0; - $start = ($pagenum - 1) * $per_page; - $end = $start + $per_page; - - foreach ( $pages as $page ) { - if ( $count >= $end ) - break; - - if ( $count >= $start ) - echo "\t" . display_page_row( $page, $level ); - - $count++; - - if ( isset($children_pages) ) - _page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page ); - } - - // if it is the last pagenum and there are orphaned pages, display them with paging as well - if ( isset($children_pages) && $count < $end ){ - foreach( $children_pages as $orphans ){ - foreach ( $orphans as $op ) { - if ( $count >= $end ) - break; - if ( $count >= $start ) - echo "\t" . display_page_row( $op, 0 ); - $count++; - } - } - } + foreach ( $pages as $page ) + echo "\t" . display_page_row( $page ); } -/* - * Given a top level page ID, display the nested hierarchy of sub-pages - * together with paging support - */ /** * {@internal Missing Short Description}} * * @since unknown * - * @param unknown_type $children_pages - * @param unknown_type $count - * @param unknown_type $parent - * @param unknown_type $level - * @param unknown_type $pagenum - * @param unknown_type $per_page - */ -function _page_rows( &$children_pages, &$count, $parent, $level, $pagenum, $per_page ) { - - if ( ! isset( $children_pages[$parent] ) ) - return; - - $start = ($pagenum - 1) * $per_page; - $end = $start + $per_page; - - foreach ( $children_pages[$parent] as $page ) { - - if ( $count >= $end ) - break; - - // If the page starts in a subtree, print the parents. - if ( $count == $start && $page->post_parent > 0 ) { - $my_parents = array(); - $my_parent = $page->post_parent; - while ( $my_parent) { - $my_parent = get_post($my_parent); - $my_parents[] = $my_parent; - if ( !$my_parent->post_parent ) - break; - $my_parent = $my_parent->post_parent; - } - $num_parents = count($my_parents); - while( $my_parent = array_pop($my_parents) ) { - echo "\t" . display_page_row( $my_parent, $level - $num_parents ); - $num_parents--; - } - } - - if ( $count >= $start ) - echo "\t" . display_page_row( $page, $level ); - - $count++; - - _page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page ); - } - - unset( $children_pages[$parent] ); //required in order to keep track of orphans -} - -/** - * {@internal Missing Short Description}} - * - * @since unknown - * * @param unknown_type $user_object * @param unknown_type $style * @param unknown_type $role