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; |
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 | |
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; |
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 |
| 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 | ); |
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 | ); |
| 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; |
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 | ); |
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 | |
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. |
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 | |
| 1292 | function 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 | |
| 1301 | function 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 | */ |
| 1317 | function 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 ); |