Skip to:
Content

BuddyPress.org

Ticket #4140: 4140.02.patch

File 4140.02.patch, 53.6 KB (added by boonebgorges, 13 years ago)
  • bp-core/bp-core-catchuri.php

    diff --git bp-core/bp-core-catchuri.php bp-core/bp-core-catchuri.php
    index 0c4060a..a922faa 100644
     
    11<?php
    22
    33/**
    4  * BuddyPress URI catcher
     4 * BUDDYPRESS ROUTER
     5 * Analyzes a URI passed from the web server and determines the correct page module to send the request to.
    56 *
    6  * Functions for parsing the URI and determining which BuddyPress template file
    7  * to use on-screen.
    8  *
    9  * Based on contributions from: Chris Taylor - http://www.stillbreathing.co.uk/
    10  * Modified for BuddyPress by: Andy Peatling - http://apeatling.wordpress.com/
     7 * @version 1.6
     8 * @since 1.6
     9 * @package Core
     10 * @subpackage Router
     11 * @license GPL v2.0
    1112 *
    12  * @package BuddyPress
    13  * @subpackage Core
     13 * ========================================================================================================
    1414 */
    1515
    16 // Exit if accessed directly
    17 if ( !defined( 'ABSPATH' ) ) exit;
     16class BP_router {
    1817
    19 /**
    20  * Analyzes the URI structure and breaks it down into parts for use in code.
    21  * BuddyPress can use complete custom friendly URI's without the user having to
    22  * add new re-write rules. Custom components are able to use their own custom
    23  * URI structures with very little work.
    24  *
    25  * @package BuddyPress Core
    26  * @since BuddyPress (r100)
    27  *
    28  * The URI's are broken down as follows:
    29  *   - http:// domain.com / members / andy / [current_component] / [current_action] / [action_variables] / [action_variables] / ...
    30  *   - OUTSIDE ROOT: http:// domain.com / sites / buddypress / members / andy / [current_component] / [current_action] / [action_variables] / [action_variables] / ...
    31  *
    32  *      Example:
    33  *    - http://domain.com/members/andy/profile/edit/group/5/
    34  *    - $bp->current_component: string 'xprofile'
    35  *    - $bp->current_action: string 'edit'
    36  *    - $bp->action_variables: array ['group', 5]
    37  *
    38  */
    39 function bp_core_set_uri_globals() {
    40         global $bp, $current_blog, $wp_rewrite;
    41        
    42         // Don't catch URIs on non-root blogs unless multiblog mode is on
    43         if ( !bp_is_root_blog() && !bp_is_multiblog_mode() )
    44                 return false;
    4518
    46         // Define local variables
    47         $root_profile = $match   = false;
    48         $key_slugs    = $matches = $uri_chunks = array();
     19        var $http_referer;                  // $_SERVER['HTTP_REFERER'] sent in from the web server
     20        var $request_uri;                   // $_SERVER['REQUEST_URI'] sent in from the web server
     21        var $wp_http_referer;               // $_REQUEST['_wp_http_referer'] sent in from the web server
    4922
    50         // Fetch all the WP page names for each component
    51         if ( empty( $bp->pages ) )
    52                 $bp->pages = bp_core_get_directory_pages();
     23        var $bp;                            // Local copy of $bp singleton
    5324
    54         // Ajax or not?
    55         if ( strpos( $_SERVER['REQUEST_URI'], 'wp-load.php' ) )
    56                 $path = bp_core_referrer();
    57         else
    58                 $path = esc_url( $_SERVER['REQUEST_URI'] );
     25        var $wpdb;                          // Local copy of $wpdb singleton
     26        var $wp_query;                      // Local copy of $wp_query singleton
     27        var $current_blog;                  // Local copy of WordPress $current_blog global
     28        var $current_site;                  // Local copy of WordPress $current_site global
    5929
    60         // Filter the path
    61         $path = apply_filters( 'bp_uri', $path );
     30        var $walk;                          // Walk array for current URI
     31        var $flat_pages;                    // The page tree for the root blog or current blog as a flat array
     32        var $lofted_pages;                  // The page tree for the root blog or current blog as a hierarchical array
     33        var $intersect;                     // Intersect object
    6234
    63         // Take GET variables off the URL to avoid problems
    64         $path = strtok( $path, '?' );
     35        var $unit_test = false;             // Set true to disable die() calls in template loader methods
    6536
    66         // Fetch current URI and explode each part separated by '/' into an array
    67         $bp_uri = explode( '/', $path );
    6837
    69         // Loop and remove empties
    70         foreach ( (array) $bp_uri as $key => $uri_chunk ) {
    71                 if ( empty( $bp_uri[$key] ) ) {
    72                         unset( $bp_uri[$key] );
    73                 }
     38        // ================================================================================================================
     39
     40
     41        function BP_router($args=null) {
     42
     43                $this->__construct($args);
    7444        }
    7545
    76         // If running off blog other than root, any subdirectory names must be
    77         // removed from $bp_uri. This includes two cases:
    78         //
    79         //    1. when WP is installed in a subdirectory,
    80         //    2. when BP is running on secondary blog of a subdirectory
    81         //       multisite installation. Phew!
    82         if ( is_multisite() && !is_subdomain_install() && ( bp_is_multiblog_mode() || 1 != bp_get_root_blog_id() ) ) {
     46        function __construct($args=null) {
    8347
    84                 // Blow chunks
    85                 $chunks = explode( '/', $current_blog->path );
     48                // Handle dependency-injection for unit tests
     49                if($args){
    8650
    87                 // If chunks exist...
    88                 if ( !empty( $chunks ) ) {
     51                        $this->http_referer = &$args['http_referer'];
     52                        $this->request_uri = &$args['request_uri'];
     53                        $this->wp_http_referer = &$args['wp_http_referer'];
    8954
    90                         // ...loop through them...
    91                         foreach( $chunks as $key => $chunk ) {
    92                                 $bkey = array_search( $chunk, $bp_uri );
     55                        $this->bp = &$args['bp'];
    9356
    94                                 // ...and unset offending keys
    95                                 if ( false !== $bkey ) {
    96                                         unset( $bp_uri[$bkey] );
    97                                 }
     57                        $this->wpdb = &$args['wpdb'];
     58                        $this->wp_query = &$args['wp_query'];
     59                        $this->current_blog = &$args['current_blog'];
     60                        $this->current_site = &$args['current_site'];
    9861
    99                                 $bp_uri = array_values( $bp_uri );
    100                         }
     62                        $this->walk = &$args['walk'];
     63                        $this->flat_pages = &$args['flat_pages'];
     64                        $this->lofted_pages = &$args['lofted_pages'];
     65                        $this->intersect = &$args['intersect'];
     66
     67                }
     68                else {
     69
     70                        global $bp;
     71                        global $wpdb, $wp_query, $current_blog, $current_site;
     72
     73                        $this->http_referer = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '';
     74                        $this->request_uri = $_SERVER['REQUEST_URI'];
     75                        $this->wp_http_referer = isset( $_REQUEST['_wp_http_referer'] ) ? $_REQUEST['_wp_http_referer'] : '';
     76
     77                        $this->bp = &$bp;
     78
     79                        $this->wpdb = &$wpdb;
     80                        $this->wp_query = &$wp_query;
     81                        $this->current_blog = &$current_blog;
     82                        $this->current_site = &$current_site;
     83
     84                        $this->walk = null;
     85                        $this->flat_pages = null;
     86                        $this->lofted_pages = null;
     87                        $this->intersect = null;
    10188                }
     89
    10290        }
    10391
    104         // Get site path items
    105         $paths = explode( '/', bp_core_get_site_path() );
    10692
    107         // Take empties off the end of path
    108         if ( empty( $paths[count( $paths ) - 1] ) )
    109                 array_pop( $paths );
     93        /**
     94         * Given a URI owned by BuddyPress, load the correct templates
     95         *
     96         * @version 1.6
     97         * @since 1.6
     98         */
     99
     100        public function route(&$status=null, &$error=null) {
     101
    110102
    111         // Take empties off the start of path
    112         if ( empty( $paths[0] ) )
    113                 array_shift( $paths );
     103                // Reset the global component, action, and item variables
     104                // ===============================================================
    114105
    115         // Reset indexes
    116         $bp_uri = array_values( $bp_uri );
    117         $paths  = array_values( $paths );
     106                $this->bp->current_component = "";
     107                $this->bp->current_action = "";
     108                $this->bp->current_item = "";
     109                $this->bp->action_variables = array();
     110                $this->bp->displayed_user->id = null;
    118111
    119         // Unset URI indices if they intersect with the paths
    120         foreach ( (array) $bp_uri as $key => $uri_chunk ) {
    121                 if ( isset( $paths[$key] ) && $uri_chunk == $paths[$key] ) {
    122                         unset( $bp_uri[$key] );
     112                // Don't catch URIs on non-root blogs unless multiblog mode is on
     113                // ===============================================================
     114
     115                if( !bp_is_root_blog() && !bp_is_multiblog_mode() ){
     116
     117                        $status = array(
     118                                'numeric'=>0,   // We normally start numbering at 1 but this is a
     119                                                // special case due to the &$status reference from
     120                                                // the matchComponent() method
     121
     122                                'text'=>"Multiblog mode is off and URI was on non-root blog",
     123                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     124                        );
     125                        return false;
     126                }
     127
     128                // Convert the URI passed by the web server into a walk array
     129                // ===============================================================
     130
     131                $this->walk = self::buildWalk(&$walk_error);
     132
     133                if($walk_error){
     134
     135                        $error = array(
     136                                'numeric'=>1,
     137                                'text'=>"Walk error",
     138                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     139                                'child'=>$walk_error
     140                        );
     141                        return false;
     142                }
     143
     144                // Intersect the walk array with the site's page tree
     145                // ===============================================================
     146
     147                $this->intersect = self::pageIntersect($this->walk, &$intersect_error);
     148
     149                if($intersect_error){
     150
     151                        $error = array(
     152                                'numeric'=>2,
     153                                'text'=>"Intersect error",
     154                                'data'=>array('walk'=>$this->walk),
     155                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     156                                'child'=>$intersect_error
     157                        );
     158                        return false;
     159                }
     160
     161                // Match the intersect to a BuddyPress component, and set the global BP variables
     162                // ===============================================================
     163
     164                $result = self::matchComponent($this->intersect, &$status, &$match_error);
     165
     166                if($match_error){
     167
     168                        $error = array(
     169                                'numeric'=>3,
     170                                'text'=>"Match error",
     171                                'data'=>array('intersect'=>$this->intersect),
     172                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     173                                'child'=>$match_error
     174                        );
     175                        return false;
    123176                }
     177
     178                return $result;
     179
    124180        }
    125181
    126         // Reset the keys by merging with an empty array
    127         $bp_uri = array_merge( array(), $bp_uri );
    128182
    129         // If a component is set to the front page, force its name into $bp_uri
    130         // so that $current_component is populated (unless a specific WP post is being requested
    131         // via a URL parameter, usually signifying Preview mode)
    132         if ( 'page' == get_option( 'show_on_front' ) && get_option( 'page_on_front' ) && empty( $bp_uri ) && empty( $_GET['p'] ) && empty( $_GET['page_id'] ) ) {
    133                 $post = get_post( get_option( 'page_on_front' ) );
    134                 if ( !empty( $post ) ) {
    135                         $bp_uri[0] = $post->post_name;
     183        /**
     184         * Given a URI from the web server, create a walk array
     185         *
     186         * @link http://en.wikipedia.org/wiki/Glossary_of_graph_theory#Walks
     187         * @version 1.6
     188         * @since 1.6
     189         * @return array $walk | Walk array
     190         */
     191
     192        public function buildWalk(&$error=null) {
     193
     194
     195                if ( strpos($this->request_uri, 'wp-load.php') ){
     196
     197                        // Try to match on the $_REQUEST['_wp_http_referer'] variable
     198                        if( !empty($this->_wp_http_referer) ){
     199
     200                                $ref = $this->_wp_http_referer;
     201                        }
     202                        // Otherwise, try to match on the $_SERVER['http_referer'] variable
     203                        elseif( !empty($this->http_referer) ){
     204
     205                                $ref = $this->http_referer;
     206                        }
     207
     208                        // If the $_SERVER['request_uri'] variable is NULL, or if the referer
     209                        // is pointing to itself, this is not a valid request
     210
     211                        if($ref == $this->request_uri){
     212
     213                                $error = array(
     214                                        'numeric'=>1,
     215                                        'text'=>"Invalid AJAX referer",
     216                                        'data'=>array(  "_wp_http_referer"=>$this->_wp_http_referer,
     217                                                        "http_referer"=>$this->http_referer,
     218                                                        "request_uri"=>$this->request_uri
     219                                                     ),
     220                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     221                                        'child'=>null
     222                                );
     223                                return false;
     224                        }
     225
     226                        // The $_wp_http_referer and $http_referer variables have the structure
     227                        // "http://site.com/foo/bar/baz" so we remove the "http://site.com/" from
     228                        // the string to make it have the same structure as $request_uri
     229
     230                        $referer = explode('/', $ref);
     231                        unset($referer[0], $referer[1], $referer[2]);
     232                        $raw_uri = implode('/', $referer);
     233
     234                }
     235                else {
     236                        // The $request_uri variable has the structure "/foo/bar/baz"
     237                        $raw_uri = esc_url($this->request_uri);
     238                }
     239
     240
     241                // Parse the URI into an array of tokens
     242                // =================================================
     243
     244                $raw_uri = apply_filters('bp_uri', $raw_uri);
     245                $parsed_uri = parse_url($raw_uri);
     246
     247                if(!$parsed_uri){
     248
     249                        $error = array(
     250                                'numeric'=>2,
     251                                'text'=>"Couldn't parse supplied URI string",
     252                                'data'=>$raw_uri,
     253                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     254                                'child'=>null
     255                        );
     256                        return false;
     257                }
     258
     259                // Strip any surplus "/" characters from the URI string, and
     260                // explode it into a walk array
     261                $walk = explode('/', trim($parsed_uri["path"], '/') );
     262
     263                // Any subdirectory names must be removed from $bp_uri. This includes two cases:
     264                // a) when WP is installed in a subdirectory,
     265                // b) when BP is running on secondary blog of a subdirectory multisite install
     266
     267                $base_walk = explode( '/', trim($this->current_blog->path, '/') );
     268                $base_count = count($base_walk);
     269
     270                if($base_count > 0){
     271
     272                        // Remove the base tokens from the walk array while
     273                        // simultaneously re-basing the array
     274
     275                        $temp_walk = array();
     276                        $intersect_count = 0;
     277                        foreach($walk as $index => $token){
     278
     279                                if( isset( $base_walk[$index] ) && $token == $base_walk[$index] ){
     280
     281                                        $intersect_count++;
     282                                }
     283                                else {
     284                                        $temp_walk[] = $token;
     285                                }
     286                        }
     287                        unset($index, $token);
     288
     289                        // If any tokens in the base array fail to intersect with
     290                        // walk array, this is not a valid URI
     291
     292                        if($base_count != $intersect_count){
     293
     294                                $error = array(
     295                                        'numeric'=>3,
     296                                        'text'=>"Malformed base URI",
     297                                        'data'=>array(
     298                                                        "walk"=>$walk,
     299                                                        "base_tokens"=>$base_walk,
     300                                                        "result"=>$temp_walk
     301                                         ),
     302                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     303                                        'child'=>null
     304                                );
     305                                return false;
     306                        }
     307
     308                        $walk = $temp_walk;
     309
    136310                }
     311
     312                return $walk;
     313
    137314        }
    138315
    139         // Keep the unfiltered URI safe
    140         $bp->unfiltered_uri = $bp_uri;
    141316
    142         // Get slugs of pages into array
    143         foreach ( (array) $bp->pages as $page_key => $bp_page )
    144                 $key_slugs[$page_key] = trailingslashit( '/' . $bp_page->slug );
     317        /**
     318         * Intersect a walk with the site's pages tree, returning the endpoint id,
     319         * endpoint slug, and transect array
     320         *
     321         * @link http://en.wikipedia.org/wiki/Tree_(graph_theory)
     322         * @link http://en.wikipedia.org/wiki/Union_(set_theory)
     323         * @version 1.6
     324         * @since 1.6
     325         * @param array $walk | Walk array
     326         * @return array $result | Result array
     327         */
    145328
    146         // Bail if keyslugs are empty, as BP is not setup correct
    147         if ( empty( $key_slugs ) )
    148                 return;
     329        public function pageIntersect($walk, &$error=null) {
    149330
    150         // Loop through page slugs and look for exact match to path
    151         foreach ( $key_slugs as $key => $slug ) {
    152                 if ( $slug == $path ) {
    153                         $match      = $bp->pages->{$key};
    154                         $match->key = $key;
    155                         $matches[]  = 1;
    156                         break;
     331
     332                // Fetch the site's pages and loft them into a hierarchical tree
     333                // ==============================================================
     334
     335                $this->flat_pages = self::getPageHierarchy(&$pages_error);
     336
     337                if($pages_error){
     338
     339                        $error = array(
     340                                'numeric'=>1,
     341                                'text'=>"Error fetching site pages",
     342                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     343                                'child'=>$pages_error
     344                        );
     345                        return false;
     346                }
     347
     348                $this->lofted_pages = self::loftHierarchy($this->flat_pages, &$loft_error);
     349
     350                if($loft_error){
     351
     352                        $error = array(
     353                                'numeric'=>2,
     354                                'text'=>"Error lofting pages array",
     355                                'data'=>array('flat_pages'=>$this->flat_pages),
     356                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     357                                'child'=>$loft_error
     358                        );
     359                        return false;
     360                }
     361
     362
     363                // Intersect the walk array with the pages tree
     364                // ==============================================================
     365
     366                $intersect = self::walkIntersectTree($walk, $this->lofted_pages, &$intersect_error);
     367
     368                if($intersect_error){
     369
     370                        $error = array(
     371                                'numeric'=>3,
     372                                'text'=>"Error intersecting walk with pages tree",
     373                                'data'=>array("walk"=>$walk, "lofted_pages"=>$this->lofted_pages),
     374                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     375                                'child'=>$intersect_error
     376                        );
     377                        return false;
    157378                }
     379
     380                return $intersect;
     381
    158382        }
    159        
    160         // No exact match, so look for partials
    161         if ( empty( $match ) ) {
    162383
    163                 // Loop through each page in the $bp->pages global
    164                 foreach ( (array) $bp->pages as $page_key => $bp_page ) {
    165384
    166                         // Look for a match (check members first)
    167                         if ( in_array( $bp_page->name, (array) $bp_uri ) ) {
     385        /**
     386         * Determine which BP component (if any) matches a given transect
     387         *
     388         * @link http://en.wikipedia.org/wiki/Cycle_(graph_theory)
     389         * @link http://en.wikipedia.org/wiki/Cycle_detection
     390         * @version 1.6
     391         * @since 1.6
     392         * @param array $intersect | Intersect array
     393         * @param array $status | Reason no match was found
     394         * @return bool $result | True on match. False on no match.
     395         */
     396
     397        public function matchComponent($intersect, &$status, &$error=null) {
     398
     399                $route_found = false;
     400                $transect = $intersect["transect"];
     401
     402                // CASE 1: Front-page component
     403                // ====================================================================
     404                if( $intersect["endpoint_id"] === null ){
    168405
    169                                 // Match found, now match the slug to make sure.
    170                                 $uri_chunks = explode( '/', $bp_page->slug );
     406                        // If a component is set to the front page, and the user is not requesting
     407                        // a specific post via a URL parameter, we have a match
    171408
    172                                 // Loop through uri_chunks
    173                                 foreach ( (array) $uri_chunks as $key => $uri_chunk ) {
     409                        $not_preview_mode = ( empty($_GET['p']) && empty($_GET['page_id']) );
    174410
    175                                         // Make sure chunk is in the correct position
    176                                         if ( !empty( $bp_uri[$key] ) && ( $bp_uri[$key] == $uri_chunk ) ) {
    177                                                 $matches[] = 1;
     411                        if($not_preview_mode){
    178412
    179                                         // No match
    180                                         } else {
    181                                                 $matches[] = 0;
     413                                $show_page_on_front = (get_option('show_on_front') == 'page'); // Note comparison operator
     414                                $post_id = get_option('page_on_front');
     415
     416                                if($show_page_on_front && $post_id){
     417
     418                                        $post = get_post($post_id);
     419
     420                                        if( !empty($post) ){
     421
     422                                                $this->bp->current_component = (string)$post->post_name;
     423                                                $route_found = true;
     424
     425                                                $status = array(
     426                                                        'numeric'=>1,
     427                                                        'text'=>"Successful match on front-page component.",
     428                                                        'data'=>array('current_component'=>$this->bp->current_component,
     429                                                                      'post_id'=>$post_id,
     430                                                                      'post'=>$post ),
     431                                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     432                                                );
     433                                        }
     434                                        else {
     435
     436                                                $error = array(
     437                                                        'numeric'=>1,
     438                                                        'text'=>"Site front page set to component, but component's post was empty",
     439                                                        'data'=>array("post_id"=>$post_id),
     440                                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     441                                                        'child'=>null
     442                                                );
     443                                                return false;
    182444                                        }
    183445                                }
     446                        }
    184447
    185                                 // Have a match
    186                                 if ( !in_array( 0, (array) $matches ) ) {
    187                                         $match      = $bp_page;
    188                                         $match->key = $page_key;
    189                                         break;
    190                                 };
     448                        if(!$route_found){
    191449
    192                                 // Unset matches
    193                                 unset( $matches );
     450                                $status = array(
     451                                        'numeric'=>2,
     452                                        'text'=>"Site front page with no components active on front page.",
     453                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     454                                );
     455
     456                                return false;
    194457                        }
    195458
    196                         // Unset uri chunks
    197                         unset( $uri_chunks );
    198459                }
    199         }
    200460
    201         // URLs with BP_ENABLE_ROOT_PROFILES enabled won't be caught above
    202         if ( empty( $matches ) && bp_core_enable_root_profiles() ) {
     461                // CASE 2: Any non-nested component
     462                // ====================================================================
    203463
    204                 // Switch field based on compat
    205                 $field = bp_is_username_compatibility_mode() ? 'login' : 'slug';
     464                if(!$this->bp->current_component){
    206465
    207                 // Make sure there's a user corresponding to $bp_uri[0]
    208                 if ( !empty( $bp->pages->members ) && !empty( $bp_uri[0] ) && $root_profile = get_user_by( $field, $bp_uri[0] ) ) {
     466                        $this->bp->current_component = self::getPrimaryComponentName($intersect["endpoint_name"], &$primary_component_error);
    209467
    210                         // Force BP to recognize that this is a members page
    211                         $matches[]  = 1;
    212                         $match      = $bp->pages->members;
    213                         $match->key = 'members';
     468                        if($primary_component_error){
     469
     470                                $error = array(
     471                                        'numeric'=>2,
     472                                        'text'=>"Error fetching primary component name",
     473                                        'data'=>array("endpoint_name"=>$intersect["endpoint_name"]),
     474                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     475                                        'child'=>$primary_component_error
     476                                );
     477                                return false;
     478                        }
    214479
    215                         // Without the 'members' URL chunk, WordPress won't know which page to load
    216                         // This filter intercepts the WP query and tells it to load the members page
    217                         add_filter( 'request', create_function( '$query_args', '$query_args["pagename"] = "' . $match->name . '"; return $query_args;' ) );
     480                        if($this->bp->current_component){
     481
     482                                $status = array(
     483                                        'numeric'=>3,
     484                                        'text'=>"Successful match on primary component",
     485                                        'data'=>array('current_component'=>$this->bp->current_component),
     486                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     487                                );
     488
     489                        }
    218490                }
    219         }
    220491
    221         // Search doesn't have an associated page, so we check for it separately
    222         if ( !empty( $bp_uri[0] ) && ( bp_get_search_slug() == $bp_uri[0] ) ) {
    223                 $matches[]   = 1;
    224                 $match       = new stdClass;
    225                 $match->key  = 'search';
    226                 $match->slug = bp_get_search_slug();
    227         }
    228492
    229         // This is not a BuddyPress page, so just return.
    230         if ( empty( $matches ) )
    231                 return false;
     493                // CASE 3: Root profile
     494                // ====================================================================
    232495
    233         $wp_rewrite->use_verbose_page_rules = false;
     496                if (    !$this->bp->current_component                                           // 1) Has not matched a component in an earlier stage
     497                        && !empty($transect)                                                    // 2) There are tokens in the transect
     498                        && !empty($this->bp->pages->members)                                    // 3) Members component is active
     499                        && defined( 'BP_ENABLE_ROOT_PROFILES' ) && BP_ENABLE_ROOT_PROFILES )    // 4) Root profiles constant is defined and true
     500                {
    234501
    235         // Find the offset. With $root_profile set, we fudge the offset down so later parsing works
    236         $slug       = !empty ( $match ) ? explode( '/', $match->slug ) : '';
    237         $uri_offset = empty( $root_profile ) ? 0 : -1;
     502                        // Shift the user name off the transect
     503                        $user_name = array_shift($transect);
    238504
    239         // Rejig the offset
    240         if ( !empty( $slug ) && ( 1 < count( $slug ) ) ) {
    241                 array_pop( $slug );
    242                 $uri_offset = count( $slug );
    243         }
     505                        // Switch the user_id based on compatibility mode
     506                        if( bp_is_username_compatibility_mode() ){
    244507
    245         // Global the unfiltered offset to use in bp_core_load_template().
    246         // To avoid PHP warnings in bp_core_load_template(), it must always be >= 0
    247         $bp->unfiltered_uri_offset = $uri_offset >= 0 ? $uri_offset : 0;
     508                                $user_id = (int) bp_core_get_userid( urldecode($user_name) );
     509                        }
     510                        else {
     511                                $user_id = (int) bp_core_get_userid_from_nicename( urldecode($user_name) );
     512                        }
    248513
    249         // We have an exact match
    250         if ( isset( $match->key ) ) {
     514                        if($user_id){
    251515
    252                 // Set current component to matched key
    253                 $bp->current_component = $match->key;
     516                                $this->bp->current_component = "members";
     517                                $this->bp->displayed_user->id = $user_id;
    254518
    255                 // If members component, do more work to find the actual component
    256                 if ( 'members' == $match->key ) {
     519                                $status = array(
     520                                        'numeric'=>4,
     521                                        'text'=>"Successful match on root profile",
     522                                        'data'=>array('current_component'=>$this->bp->current_component),
     523                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     524                                );
    257525
    258                         // Viewing a specific user
    259                         if ( !empty( $bp_uri[$uri_offset + 1] ) ) {
     526                                // Without the 'members' URL chunk, WordPress won't know which page to load,
     527                                // so this filter intercepts the WP query and tells it to load the members page
    260528
    261                                 // Switch the displayed_user based on compatbility mode
    262                                 if ( bp_is_username_compatibility_mode() ) {
    263                                         $bp->displayed_user->id = (int) bp_core_get_userid( urldecode( $bp_uri[$uri_offset + 1] ) );
    264                                 } else {
    265                                         $bp->displayed_user->id = (int) bp_core_get_userid_from_nicename( urldecode( $bp_uri[$uri_offset + 1] ) );
    266                                 }
     529                                $function_string  = '$query_args["pagename"] = "';
     530                                $function_string .= $this->bp->pages->members->name;
     531                                $function_string .= '"; return $query_args;';
    267532
    268                                 if ( !bp_displayed_user_id() ) {
     533                                add_filter( 'request', create_function('$query_args', $function_string) );
    269534
    270                                         // Prevent components from loading their templates
    271                                         $bp->current_component = '';
     535                        }
     536                        else {
     537
     538                                $status = array(
     539                                        'numeric'=>5,
     540                                        'text'=>"Root profiles enabled. No matching user.",
     541                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     542                                );
     543                                return false;
     544                        }
    272545
    273                                         bp_do_404();
    274                                         return;
    275                                 }
     546                }
     547
     548                // CASE 4: No match
     549                // ====================================================================
     550
     551                if(!$this->bp->current_component){
     552
     553                        $status = array(
     554                                'numeric'=>6,
     555                                'text'=>"No matching components",
     556                                'data'=>array('intersect'=>$this->intersect, 'walk'=>$this->walk),
     557                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     558                        );
     559                        return false;
     560                }
     561
     562                // Members Component secondary processing
     563                // ====================================================================
     564
     565                if( ($this->bp->current_component == "members") && !empty($transect) ){
     566
     567                        // If the component is "members", the transect must either contain no tokens (show all users on site),
     568                        // or the first token in the transect must be a valid user name (show single user)
     569
     570                        $user_name = array_shift($transect);
     571
     572                        // Switch the user_id based on compatibility mode
     573                        if( bp_is_username_compatibility_mode() ){
    276574
    277                                 // If the displayed user is marked as a spammer, 404 (unless logged-
    278                                 // in user is a super admin)
    279                                 if ( bp_displayed_user_id() && bp_is_user_spammer( bp_displayed_user_id() ) ) {
    280                                         if ( bp_current_user_can( 'bp_moderate' ) ) {
    281                                                 bp_core_add_message( __( 'This user has been marked as a spammer. Only site admins can view this profile.', 'buddypress' ), 'warning' );
    282                                         } else {
     575                                $user_id = (int) bp_core_get_userid( urldecode($user_name) );
     576                        }
     577                        else {
     578                                $user_id = (int) bp_core_get_userid_from_nicename( urldecode($user_name) );
     579                        }
     580
     581                        // CASE 1: Token in first transect position isn't a valid user_id
     582                        // ---------------------------------------------------------------------------------------
     583                        if( empty($user_id) ){
     584
     585                                $this->bp->current_component = null;    // Prevent components from loading their templates
     586                                bp_do_404();
     587
     588                                $status = array(
     589                                        'numeric'=>7,
     590                                        'text'=>"Match on members component, but user_id is not valid.",
     591                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     592                                );
     593                                return false;
     594
     595                        }
     596
     597                        elseif( !empty($user_id) ){
     598
     599                                $this->bp->displayed_user->id = $user_id;
     600
     601                                // CASE 2: Token in first transect position matches a user_id that
     602                                // has been marked as a spammer
     603                                // ---------------------------------------------------------------------------------------
     604                                if( bp_is_user_spammer($user_id) ){
     605
     606                                        if( is_super_admin() ){
     607
     608                                                bp_core_add_message( __( 'This user has been marked as a spammer. Only site admins can view this profile.', 'buddypress' ), 'error' );
     609                                        }
     610                                        else {
     611                                                // If the user viewing the profile is not a super-admin, hide the page
    283612                                                bp_do_404();
    284                                                 return;
     613
     614                                                $status = array(
     615                                                        'numeric'=>8,
     616                                                        'text'=>"Match on members component, but user_id is marked as a spammer and viewer is not a super-admin.",
     617                                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     618                                                );
     619                                                return false;
    285620                                        }
     621
    286622                                }
     623                                // CASE 3: There are one or more tokens left in the transect after the user_name has
     624                                // been shifted-out. This means we have a secondary component nested inside the members
     625                                // component. The secondary component's *slug* will be the first token in the transect. We
     626                                // have to set $this->bp->current_component to the *name* of the secondary component so
     627                                // BP loads the correct template chain.
     628                                // ---------------------------------------------------------------------------------------
     629                                elseif( count($transect) > 0) {
     630
     631                                        $current_component_slug = array_shift($transect);
     632
     633                                        // CASE 3A: Match against the "primary" components that can exist both as a top-level
     634                                        // page and a secondary page nested beneath the "members" component. External plugins
     635                                        // following the "BuddyPress Example Component" pattern will appear in this array.
     636                                        //
     637                                        // TODO: This creates a cardinality problem. Primary components will appear at
     638                                        // both "example.com/members/membername/slug_name" and "example.com/slug_name". This
     639                                        // is further complicated by the fact that some components use the alias location as a
     640                                        // *context*, for example, "activity" at the root node shows activity for all users on
     641                                        // the site, but "activity" nested in the "members" component shows activity for a user.
     642                                        // There needs to be a set of configuration options on the admin back-end to specify
     643                                        // which location to use for a given component. Note that this is a legacy problem with
     644                                        // the original BP router design and we have emulated it for compatibility.
     645                                        // ---------------------------------------------------------------------------------------
     646
     647                                        $this->bp->current_component = self::getPrimaryComponentName($current_component_slug, &$primary_component_error);
     648
     649                                        if($primary_component_error){
     650
     651                                                $error = array(
     652                                                        'numeric'=>3,
     653                                                        'text'=>"Error fetching primary component name",
     654                                                        'data'=>array("current_component_slug"=>$current_component_slug),
     655                                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     656                                                        'child'=>$primary_component_error
     657                                                );
     658                                                return false;
     659                                        }
     660
     661                                        if($this->bp->current_component != null){
     662
     663                                                $status = array(
     664                                                        'numeric'=>9,
     665                                                        'text'=>"Match on members component with primary nested component",
     666                                                        'data'=>array(  'bp_pages'=>$this->bp->pages,
     667                                                                        'active_components'=>$this->bp->active_components,
     668                                                                        'current_component_slug'=>$current_component_slug,
     669                                                                        "component"=>$this->bp->current_component),
     670                                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     671                                                );
     672                                        }
     673                                        else {
     674
     675                                                // CASE 3B: Match against the "secondary" components that can only exist as a secondary
     676                                                // page nested beneath the "members" component. Matching is determined by the component's
     677                                                // action functions, which hook on the 'bp_init' action. Action functions are located
     678                                                // in "/component_name/bp-component_name-actions.php".
     679                                                // ---------------------------------------------------------------------------------------
     680
     681                                                $this->bp->current_component = $current_component_slug;
     682
     683                                                $status = array(
     684                                                        'numeric'=>10,
     685                                                        'text'=>"Match on members component, with possible match on secondary nested component",
     686                                                        'data'=>array(  'bp_pages'=>$this->bp->pages,
     687                                                                        'active_components'=>$this->bp->active_components,
     688                                                                        'current_component_slug'=>$current_component_slug),
     689                                                        'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     690                                                );
    287691
    288                                 // Bump the offset
    289                                 if ( isset( $bp_uri[$uri_offset + 2] ) ) {
    290                                         $bp_uri                = array_merge( array(), array_slice( $bp_uri, $uri_offset + 2 ) );
    291                                         $bp->current_component = $bp_uri[0];
    292692
    293                                 // No component, so default will be picked later
    294                                 } else {
    295                                         $bp_uri                = array_merge( array(), array_slice( $bp_uri, $uri_offset + 2 ) );
    296                                         $bp->current_component = '';
     693                                        }
     694
    297695                                }
     696                                // CASE 4: There are no tokens left in the transect, so we're at the default screen
     697                                // in the members component. Allow $this->bp->current_component to
     698                                // be set automatically to the default profile
     699                                // component by BP_Members_Component::setup_globals()
     700                                // ---------------------------------------------------------------------------------------
     701                                else {
     702                                        $this->bp->current_component = '';
     703
     704                                        $status = array(
     705                                                'numeric'=>11,
     706                                                'text'=>"Match on members component with no nested component",
     707                                                'data'=>array("component"=>$this->bp->current_component),
     708                                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__
     709                                        );
     710                                }
     711
    298712
    299                                 // Reset the offset
    300                                 $uri_offset = 0;
    301713                        }
     714
     715
     716                }
     717
     718
     719                // Set BP's global variables
     720                // ====================================================================
     721
     722                if( isset($transect[0]) ){
     723
     724                        $this->bp->current_action = array_shift($transect);
     725
     726                            if( count($transect) > 0 ){
     727
     728                                    $this->bp->action_variables = $transect;
     729                            }
     730
    302731                }
     732
     733                // Set WP global variables
     734                // ====================================================================
     735                if( !empty($object_id) ){
     736
     737                        // Set WP's internal query variables to the same state they would be in if
     738                        // WP had loaded the page itself instead of BP intercepting the page load
     739                        // and replacing it with our own content
     740
     741                        // TODO: We've emulated this for compatibility. BP should try to avoid
     742                        // doing this unless actually necessary, because it costs an extra query on
     743                        // each page load.
     744
     745                        $this->wp_query->queried_object_id = $this->intersect["endpoint_id"];
     746                        $this->wp_query->queried_object    = &get_post($this->intersect["endpoint_id"]);
     747
     748                }
     749
    303750        }
    304751
    305         // Set the current action
    306         $bp->current_action = isset( $bp_uri[$uri_offset + 1] ) ? $bp_uri[$uri_offset + 1] : '';
    307752
    308         // Slice the rest of the $bp_uri array and reset offset
    309         $bp_uri      = array_slice( $bp_uri, $uri_offset + 2 );
    310         $uri_offset  = 0;
     753        /**
     754         * Returns a flat array of the site's page hierarchy
     755         *
     756         * @version 1.6
     757         * @since 1.6
     758         * @return array $result | Page hierarchy as flat array
     759         */
    311760
    312         // Set the entire URI as the action variables, we will unset the current_component and action in a second
    313         $bp->action_variables = $bp_uri;
     761        public function getPageHierarchy(&$error=null) {
    314762
    315         // Reset the keys by merging with an empty array
    316         $bp->action_variables = array_merge( array(), $bp->action_variables );
    317 }
     763                // TODO: Add caching capabilities
    318764
    319 /**
    320  * Are root profiles enabled and allowed
    321  *
    322  * @since BuddyPress (1.6)
    323  * @return bool True if yes, false if no
    324  */
    325 function bp_core_enable_root_profiles() {
     765                global $wpdb;
    326766
    327         $retval = false;
     767                // Always get page data from the root blog, except on multiblog mode, when it comes
     768                // from the current blog
    328769
    329         if ( defined( 'BP_ENABLE_ROOT_PROFILES' ) && ( true == BP_ENABLE_ROOT_PROFILES ) )
    330                 $retval = true;
     770                if( bp_is_multiblog_mode() ){
    331771
    332         return apply_filters( 'bp_core_enable_root_profiles', $retval );
    333 }
     772                        $posts_table_name = $wpdb->posts;
     773                }
     774                else {
     775                        $posts_table_name = $wpdb->get_blog_prefix( bp_get_root_blog_id() ) . 'posts';
     776                }
    334777
    335 /**
    336  * bp_core_load_template()
    337  *
    338  * Load a specific template file with fallback support.
    339  *
    340  * Example:
    341  *   bp_core_load_template( 'members/index' );
    342  * Loads:
    343  *   wp-content/themes/[activated_theme]/members/index.php
    344  *
    345  * @package BuddyPress Core
    346  * @param $username str Username to check.
    347  * @return false|int The user ID of the matched user, or false.
    348  */
    349 function bp_core_load_template( $templates ) {
    350         global $post, $bp, $wp_query, $wpdb;
     778                $sql = "SELECT ID, post_name, post_parent, post_title FROM {$posts_table_name} WHERE post_type = 'page' AND post_status != 'auto-draft'";
     779                $pages = $wpdb->get_results($sql);
     780
     781                // Trap any database errors
     782                $sql_error = mysql_error($wpdb->dbh);
     783
     784                if($sql_error){
     785
     786                        $error = array(
     787                                'numeric'=>1,
     788                                'text'=>"Database error",
     789                                'data'=>array($sql, $sql_error),
     790                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     791                                'child'=>null
     792                        );
    351793
    352         // Determine if the root object WP page exists for this request
    353         // note: get_page_by_path() breaks non-root pages
    354         if ( !empty( $bp->unfiltered_uri_offset ) ) {
    355                 if ( !$page_exists = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_name = %s", $bp->unfiltered_uri[$bp->unfiltered_uri_offset] ) ) ) {
    356794                        return false;
    357795                }
     796
     797                // Spin the SQL server's output into a useful format
     798                $result = array();
     799
     800                foreach($pages as $page){
     801
     802                        $result[$page->ID] = array( "parent"=>$page->post_parent,
     803                                                    "slug"=>$page->post_name,
     804                                                    "title"=>$page->post_title
     805                                             );
     806                }
     807                unset($page);
     808
     809                return $result;
     810
     811        }
     812
     813
     814        /**
     815         * Lofts a flat array of nodes into a rooted directed tree in O(n) time
     816         * with only O(n) extra memory. This is also known as the "in-place quick
     817         * union" algorithm.
     818         *
     819         * @link http://en.wikipedia.org/wiki/Tree_(graph_theory)
     820         * @link http://en.wikipedia.org/wiki/Glossary_of_graph_theory#Walks
     821         * @link http://en.wikipedia.org/wiki/Quicksort (in-place version)
     822         *
     823         * @version 1.6
     824         * @since 1.6
     825         * @param array $nodes | Flat array of nodes
     826         * @return array $result | Hierarchical array of nodes
     827         */
     828
     829        public function loftHierarchy($nodes) {
     830
     831                $tree = array();
     832
     833                foreach( $nodes as $node_id => $data){
     834
     835                        // Note: we can operate directly on the passed parameter, because unless
     836                        // explicitly told not to by using the "&$" sigil, PHP passes copies
     837                        // of variables into a function.
     838
     839                        $nodes[$node_id]["node_id"] = $node_id;     // Insert the node_id into each node to make the data
     840                                                                    // structure easier to use. Note the unit tests are very
     841                                                                    // picky about the order this gets done in because it
     842                                                                    // affects its position in the output array.
     843                        if( empty($data["parent"]) ){
     844
     845                                $tree["children"][$node_id] =& $nodes[$node_id];
     846                        }
     847                        else {
     848                                $nodes[$data["parent"]]["children"][$node_id] =& $nodes[$node_id];
     849                        }
     850                }
     851
     852                return $tree;
    358853        }
    359854
    360         // Set the root object as the current wp_query-ied item
    361         $object_id = 0;
    362         foreach ( (array) $bp->pages as $page ) {
    363                 if ( $page->name == $bp->unfiltered_uri[$bp->unfiltered_uri_offset] ) {
    364                         $object_id = $page->id;
     855
     856        /**
     857         * Finds the longest intersect between a walk and a tree.
     858         *
     859         * @link http://en.wikipedia.org/wiki/Glossary_of_graph_theory#Walks
     860         * @link http://en.wikipedia.org/wiki/Breadth-first_search
     861         *
     862         * @version 1.6
     863         * @since 1.6
     864         * @param array $walk | Walk array
     865         * @param array $tree | Tree array
     866         * @return array $result | Walk key and matching node id
     867         */
     868
     869        public function walkIntersectTree($walk, $tree, &$error=null) {
     870
     871
     872                if( !is_array($walk) ){
     873
     874                        $error = array(
     875                                'numeric'=>1,
     876                                'text'=>"Walk is not a valid array",
     877                                'data'=>array( "walk"=>$walk, "tree"=>$tree),
     878                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     879                                'child'=>null
     880                        );
     881                        return false;
     882                }
     883
     884                if( !is_array($tree) ){
     885
     886                        $error = array(
     887                                'numeric'=>2,
     888                                'text'=>"Tree is not a valid array",
     889                                'data'=>array( "walk"=>$walk, "tree"=>$tree),
     890                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     891                                'child'=>null
     892                        );
     893                        return false;
     894                }
     895
     896
     897                // Loop through each child node, searching for the
     898                // child node with the longest walk
     899                // ================================================
     900
     901                $min_offset = null;
     902                $min_node = null;
     903
     904                foreach( $tree["children"] as $node_id => $data){
     905
     906                        if( isset( $walk[0] ) && $data["slug"] == $walk[0] ){
     907
     908                                $reduced_walk = array_slice($walk, 1);
     909                                $intersect = self::walkIntersectTree_iterator($reduced_walk, $data);
     910
     911                                if( ($min_offset === null) || ($intersect["walk_offset"] < $min_offset) ){
     912
     913                                        $min_offset = $intersect["walk_offset"];
     914                                        $min_node = $intersect["node_id"];
     915                                }
     916                        }
     917
     918                }
     919
     920                // Return the child node with the longest walk, or if
     921                // there was no matching child node, return this node
     922                // ================================================
     923
     924                if($min_offset === null){
     925
     926                        $result = array(
     927                                            "endpoint_id"=>null,
     928                                            "endpoint_name"=>null,
     929                                            "walk_key"=>null,
     930                                            "transect"=>array()
     931                        );
     932                }
     933                else {
     934
     935                        // Convert offset to array key number so functions further down
     936                        // the chain can use array_slice() to find the tokens after the
     937                        // endpoint that correspond to actions/arguements (if they exist)
     938
     939                        $walk_key = (count($walk) - $min_offset) - 1;
     940
     941                        $result = array(
     942                                            "endpoint_id" => $min_node,
     943                                            "endpoint_name"=>$walk[$walk_key],
     944                                            "walk_key" => $walk_key,
     945                                            "transect"=>array_slice($walk, ($walk_key +1) )
     946                        );
    365947                }
     948
     949                return $result;
     950
    366951        }
    367952
    368         // Make the queried/post object an actual valid page
    369         if ( !empty( $object_id ) ) {
    370                 $wp_query->queried_object    = &get_post( $object_id );
    371                 $wp_query->queried_object_id = $object_id;
    372                 $post                        = $wp_query->queried_object;
     953
     954        /**
     955         * Finds the longest intersect between the walk and the tree.
     956         *
     957         * @link http://en.wikipedia.org/wiki/Glossary_of_graph_theory#Walks
     958         * @link http://en.wikipedia.org/wiki/Breadth-first_search
     959         *
     960         * @version 1.6
     961         * @since 1.6
     962         * @param array $walk | Walk array
     963         * @param array $tree | Tree array
     964         * @return array $result | Walk offset and matching node id
     965         */
     966
     967        public function walkIntersectTree_iterator($walk, $tree) {
     968
     969
     970                // Calculate offsets
     971                // ================================================
     972
     973                $walk_offset = count($walk);
     974
     975                if( isset( $tree['children'] ) && is_array($tree["children"]) ){
     976
     977                        $children_count = count($tree["children"]);
     978                }
     979                else {
     980                        $children_count = 0;
     981                }
     982
     983                // If either termination condition is met, return
     984                // ================================================
     985
     986                if( ($walk_offset == 0) || ($children_count == 0) ){
     987
     988                        $result = array(    "node_id"=>$tree["node_id"],
     989                                            "walk_offset"=>$walk_offset
     990                        );
     991
     992                        return $result;
     993                }
     994
     995                // Loop through each child node, searching for the
     996                // child node with the longest walk
     997                // ================================================
     998
     999                $min_offset = null;
     1000                $min_node = null;
     1001
     1002                foreach( $tree["children"] as $node_id => $data){
     1003
     1004                        if($data["slug"] == $walk[0]){
     1005
     1006                                $reduced_walk = array_slice($walk, 1);
     1007                                $intersect = self::walkIntersectTree_iterator($reduced_walk, $data);
     1008
     1009                                if( ($min_offset === null) || ($intersect["walk_offset"] < $min_offset) ){
     1010
     1011                                        $min_offset = $intersect["walk_offset"];
     1012                                        $min_node = $intersect["node_id"];
     1013                                }
     1014                        }
     1015
     1016                }
     1017
     1018                // Return the child node with the longest walk, or if
     1019                // there was no matching child node, return this node
     1020                // ================================================
     1021
     1022                if($min_offset === null){
     1023
     1024                        $result = array(
     1025                                            "node_id"=>$tree["node_id"],
     1026                                            "walk_offset"=>$walk_offset
     1027                        );
     1028                }
     1029                else {
     1030                        $result = array(
     1031                                            "node_id"=>$min_node,
     1032                                            "walk_offset"=>$min_offset
     1033                        );
     1034                }
     1035
     1036                return $result;
     1037
    3731038        }
    3741039
    375         // Define local variables
    376         $located_template   = false;
    377         $filtered_templates = array();
    3781040
    379         // Fetch each template and add the php suffix
    380         foreach ( (array) $templates as $template )
    381                 $filtered_templates[] = $template . '.php';
     1041        /**
     1042         * Checks if a slug matches an active "primary" BuddyPress component. Primary components
     1043         * are components which can exist as a top-level page on the site, and in some cases
     1044         * a secondary page nested below the "members" component. Third-party components following
     1045         * the "BuddyPress Example Component" pattern will appear in the results.
     1046         *
     1047         * @version 1.6
     1048         * @since 1.6
     1049         * @param string $slug | Name of slug to check
     1050         * @return bool/string $result | False on failure. Null on nonexistent. Name of component on success.
     1051         */
     1052
     1053        public function getPrimaryComponentName($slug, &$error=null) {
     1054
     1055
     1056                if( empty($slug) ){
     1057
     1058                        $error = array(
     1059                                'numeric'=>1,
     1060                                'text'=>"Called with empty slug",
     1061                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     1062                                'child'=>null
     1063                        );
     1064                        return false;
     1065                }
     1066
     1067                // If the BP Pages object hasn't been loaded yet, try to load it
     1068                if( empty($this->bp->pages) ){
     1069
     1070                        $this->bp->pages = self::buildDirectoryPages($this->flat_pages);
     1071                }
     1072
     1073                if( empty($this->bp->pages) ){
     1074
     1075                        $error = array(
     1076                                'numeric'=>2,
     1077                                'text'=>"Failed to load BP pages object",
     1078                                'data'=>array("bp_pages"=>$this->bp->pages),
     1079                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     1080                                'child'=>null
     1081                        );
     1082                        return false;
     1083                }
     1084
     1085
     1086                foreach($this->bp->pages as $component_name => $data){
     1087
     1088                        // NOTE: We cannot use an algorithm that checks against $this->bp->active_components,
     1089                        // because its impossible for 3rd-party components to add themselves to this array
     1090                        // using the 'bp_active_components' filter. The filter is placed so early in the call
     1091                        // stack it runs before 3rd-party components can load any of their plugin files.
    3821092
    383         // Filter the template locations so that plugins can alter where they are located
    384         $located_template = apply_filters( 'bp_located_template', locate_template( (array) $filtered_templates, false ), $filtered_templates );
    385         if ( !empty( $located_template ) ) {
     1093                        if( !array_key_exists($component_name, $this->bp->deactivated_components)   // 1) Component is active
     1094                            && $data->name == $slug )                                               // 2) Slug matches
     1095                        {
     1096                                return $component_name;
     1097                        }
     1098                }
     1099                unset($component_name, $data);
    3861100
    387                 // Template was located, lets set this as a valid page and not a 404.
    388                 status_header( 200 );
    389                 $wp_query->is_page = $wp_query->is_singular = true;
    390                 $wp_query->is_404  = false;
     1101                // Separate check for search component (because its not a real BP component,
     1102                // and its not included in the $bp->active_components array)
    3911103
    392                 do_action( 'bp_core_pre_load_template', $located_template );
     1104                if($slug == bp_get_search_slug()){
     1105
     1106                        return "search";
     1107                }
    3931108
    394                 load_template( apply_filters( 'bp_load_template', $located_template ) );
     1109                return null;
    3951110
    396                 do_action( 'bp_core_post_load_template', $located_template );
    3971111        }
    3981112
    399         // Kill any other output after this.
    400         die;
     1113        /**
     1114         * Generates the BP component pages array
     1115         *
     1116         * @version 1.6
     1117         * @since 1.6
     1118         * @param array $flat_pages | Flat array of all WordPress pages on the site
     1119         * @return obj $pages | Structured object containing page ID's, Names, and Slugs
     1120         */
     1121        function buildDirectoryPages($flat_pages, &$error=null) {
     1122
     1123
     1124                if( empty($flat_pages) ){
     1125
     1126                        $error = array(
     1127                                'numeric'=>1,
     1128                                'text'=>"Called with empty flat_pages array",
     1129                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     1130                                'child'=>null
     1131                        );
     1132                        return false;
     1133                }
     1134
     1135
     1136                $page_ids = (array)bp_core_get_directory_page_ids();
     1137
     1138                if( empty($page_ids) ){
     1139
     1140                        $error = array(
     1141                                'numeric'=>2,
     1142                                'text'=>"BP core directory page ids option is empty",
     1143                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     1144                                'child'=>null
     1145                        );
     1146                        return false;
     1147                }
     1148
     1149
     1150                $pages = new stdClass;
     1151
     1152                // Iterate through each entry in the BP pages config option
     1153                foreach( $page_ids as $component_id => $bp_page_id ) {
     1154
     1155                        // Iterate through each WP site page in the flat pages array
     1156                        foreach( $flat_pages as $wp_page_id => $data ) {
     1157
     1158                                // If the page ids match, add this page to the components array
     1159                                if( $wp_page_id == $bp_page_id ) {
     1160
     1161                                        $pages->{$component_id}->name  = $data['slug'];
     1162                                        $pages->{$component_id}->id    = $wp_page_id;
     1163                                        $pages->{$component_id}->title = $data['title'];
     1164
     1165                                        $stem = array();
     1166                                        $stem[] = $data['slug'];
     1167
     1168                                        $parent = $data['parent'];
     1169
     1170                                        // If the page is not attached to the root node, traverse the page tree backwards to the
     1171                                        // root node generating the reverse walk, then flip it and implode it to a string.
     1172
     1173                                        while( $parent != 0 ){
     1174
     1175                                                $stem[] = $flat_pages[$parent]['slug'];
     1176                                                $parent = $flat_pages[$parent]['parent'];
     1177                                        }
     1178
     1179                                        // TODO: BuddyPress incorrectly calls this a "slug", which is confusing. The correct term
     1180                                        // is a "stem" (in string form) and a "walk" (in array form).
     1181
     1182                                        $pages->{$component_id}->slug = implode( '/', array_reverse( (array)$stem ) );
     1183                                }
     1184
     1185                                unset($slug);
     1186                        }
     1187                        unset($wp_page_id, $data);
     1188
     1189                }
     1190                unset($component_id, $bp_page_id);
     1191
     1192                return apply_filters( 'bp_core_get_directory_pages', $pages );
     1193
     1194        }
     1195
     1196
     1197        /**
     1198         * Load a specific template file, with fallback support.
     1199         *
     1200         * Example: bp_core_load_template( 'members/index' );
     1201         * Loads: wp-content/themes/[activated_theme]/members/index.php
     1202         *
     1203         * @version 1.6
     1204         * @since 1.6
     1205         * @param string/array $templates | Single template name as string. Multiple template names as array of string.
     1206         * @return bool/die $result | False on failure. Loads template and terminates thread on success.
     1207         */
     1208        function loadTemplate($templates, &$error=null) {
     1209
     1210/*
     1211                if( !$this->intersect["endpoint_id"] ){
     1212
     1213                        $error = array(
     1214                                'numeric'=>1,
     1215                                'text'=>"Cannot load template because router was unable to intersect the current
     1216                                         request URI with any pages in the site's page tree.",
     1217                                'data'=>array("intersect"=>$this->intersect,
     1218                                              "walk"=>$this->walk,
     1219                                              "templates"=>$templates),
     1220                                'file'=>__FILE__, 'line'=>__LINE__, 'method'=>__METHOD__,
     1221                                'child'=>null
     1222                        );
     1223                        return false;
     1224                }
     1225*/
     1226                // Add a ".php" suffix to each template file in the $templates array
     1227                foreach( (array)$templates as $template ){
     1228
     1229                        $filtered_templates[] = $template . '.php';
     1230                }
     1231
     1232                // Filter the template locations so that plugins can alter where they are located
     1233                $located_template = apply_filters( 'bp_located_template', locate_template( (array) $filtered_templates, false ), $filtered_templates );
     1234
     1235                if($located_template){
     1236
     1237                        // Explicitly set WP's internal query variables to the correct state (because the
     1238                        // default is to 404 the page)
     1239
     1240                        $this->wp_query->is_page = true;
     1241                        $this->wp_query->is_404 = false;
     1242                        $this->wp_query->is_singular = true;
     1243
     1244                        // Explicitly set the HTTP headers. Note that this only sets the headers for the web
     1245                        // page. The web server generates its own headers for individual items such as images
     1246                        // and CSS stylesheets loaded by the page.
     1247
     1248                        $protocol = $_SERVER["SERVER_PROTOCOL"];
     1249                        $code = 200;
     1250                        $text = "OK";
     1251
     1252                        if( ($protocol != 'HTTP/1.1') && ($protocol != 'HTTP/1.0') ){
     1253
     1254                                $protocol = 'HTTP/1.0';
     1255                        }
     1256
     1257                        $status_header = "$protocol $code $text";
     1258
     1259                        header($status_header, true, $code);
     1260
     1261                        load_template( apply_filters( 'bp_load_template', $located_template ) );
     1262
     1263                }
     1264
     1265                if(!$this->unit_test){
     1266
     1267                        die;
     1268
     1269                        // TODO: It's bad practice to place silent die() calls all over an application's code because it
     1270                        // makes it very difficult to unit-test.
     1271                        //
     1272                        // The die() call above prevents WordPress from loading the template for the WP page we hijacked. If
     1273                        // it was removed, the system would display the BP version of the page, followed by the WP version of
     1274                        // the page. A better solution might be to set WP's internal variables so that it thinks it's successfully
     1275                        // loaded the page, resulting in no further output and correctly running all the WP shutdown processes.
     1276
     1277                }
     1278
     1279        }
     1280
     1281
     1282
     1283} // End class BP_router
     1284
     1285
     1286
     1287// BRIDGE FUNCTIONS
     1288// ========================================================================================================
     1289// These functions allow legacy code to access the new router class
     1290
     1291
     1292function bp_core_set_uri_globals(){
     1293
     1294        global $bp;
     1295        $bp->router = new BP_router();
     1296
     1297        $result = $bp->router->route(&$status, &$error);
     1298        return $result;
     1299}
     1300
     1301function bp_core_load_template($templates){
     1302
     1303        global $bp;
     1304        $result = $bp->router->loadTemplate($templates, &$error);
     1305        return $result;
     1306}
     1307
     1308// ========================================================================================================
     1309
     1310
     1311/**
     1312 * Are root profiles enabled and allowed
     1313 *
     1314 * @since BuddyPress (1.6)
     1315 * @return bool True if yes, false if no
     1316 */
     1317function bp_core_enable_root_profiles() {
     1318
     1319        $retval = false;
     1320
     1321        if ( defined( 'BP_ENABLE_ROOT_PROFILES' ) && ( true == BP_ENABLE_ROOT_PROFILES ) )
     1322                $retval = true;
     1323
     1324        return apply_filters( 'bp_core_enable_root_profiles', $retval );
    4011325}
    4021326
    4031327/**
    function bp_core_no_access( $args = '' ) { 
    5091433 * @since BuddyPress (1.5)
    5101434 */
    5111435function bp_core_no_access_wp_login_error() {
     1436
    5121437        global $error;
    5131438
    5141439        $error = apply_filters( 'bp_wp_login_error', __( 'You must log in to access the page you requested.', 'buddypress' ), $_REQUEST['redirect_to'] );
    5151440
    516         // shake shake shake!
     1441        // Shake the login box to show there was an error
    5171442        add_action( 'login_head', 'wp_shake_js', 12 );
     1443
    5181444}
    5191445add_action( 'login_form_bpnoaccess', 'bp_core_no_access_wp_login_error' );
    5201446
     1447
    5211448/**
    5221449 * Canonicalizes BuddyPress URLs
    5231450 *
    add_action( 'login_form_bpnoaccess', 'bp_core_no_access_wp_login_error' ); 
    5321459 * @see bp_core_new_nav_item() where $bp->canonical_stack['action'] may be set
    5331460 */
    5341461function bp_redirect_canonical() {
     1462
    5351463        global $bp;
    5361464
    5371465        if ( !bp_is_blog_page() && apply_filters( 'bp_do_redirect_canonical', true ) ) {
     1466
    5381467                // If this is a POST request, don't do a canonical redirect.
    5391468                // This is for backward compatibility with plugins that submit form requests to
    5401469                // non-canonical URLs. Plugin authors should do their best to use canonical URLs in
    5411470                // their form actions.
     1471
    5421472                if ( !empty( $_POST ) ) {
    5431473                        return;
    5441474                }
    5451475
    546                 // build the URL in the address bar
     1476                // Build the URL in the address bar
    5471477                $requested_url  = is_ssl() ? 'https://' : 'http://';
    5481478                $requested_url .= $_SERVER['HTTP_HOST'];
    5491479                $requested_url .= $_SERVER['REQUEST_URI'];
    function bp_redirect_canonical() { 
    5731503
    5741504                // Add trailing slash
    5751505                $url_stack[0] = trailingslashit( $url_stack[0] );
    576                
     1506
    5771507                // Only redirect if we've assembled a URL different from the request
    5781508                if ( $url_stack[0] !== $req_url_clean ) {
    5791509
    function bp_redirect_canonical() { 
    5911521        }
    5921522}
    5931523
     1524
    5941525/**
    5951526 * Output rel=canonical header tag for BuddyPress content
    5961527 *
    5971528 * @since 1.6
    5981529 */
    5991530function bp_rel_canonical() {
     1531
    6001532        // Build the URL in the address bar
    6011533        $requested_url  = is_ssl() ? 'https://' : 'http://';
    6021534        $requested_url .= $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    function bp_rel_canonical() { 
    6271559        echo "<link rel='canonical' href='" . esc_attr( $url_stack[0] ) . "' />\n";
    6281560}
    6291561
     1562
    6301563/**
    6311564 * Remove WordPress's really awesome canonical redirect if we are trying to load
    6321565 * BuddyPress specific content. Avoids issues with WordPress thinking that a
    function bp_rel_canonical() { 
    6391572 * @uses bp_is_blog_page()
    6401573 */
    6411574function _bp_maybe_remove_redirect_canonical() {
     1575
    6421576        if ( ! bp_is_blog_page() )
    6431577                remove_action( 'template_redirect', 'redirect_canonical' );
     1578
    6441579}
    6451580add_action( 'bp_init', '_bp_maybe_remove_redirect_canonical' );
    6461581
     1582
    6471583/**
    6481584 * Remove WordPress's rel=canonical HTML tag if we are trying to load BuddyPress
    6491585 * specific content.
    add_action( 'bp_init', '_bp_maybe_remove_redirect_canonical' ); 
    6541590 * @since 1.6
    6551591 */
    6561592function _bp_maybe_remove_rel_canonical() {
     1593
    6571594        if ( ! bp_is_blog_page() && ! is_404() ) {
    6581595                remove_action( 'wp_head', 'rel_canonical' );
    6591596                add_action( 'bp_head', 'bp_rel_canonical' );
    6601597        }
     1598
    6611599}
    6621600add_action( 'wp_head', '_bp_maybe_remove_rel_canonical', 8 );
     1601
     1602
    6631603?>
     1604 No newline at end of file