Skip to:
Content

BuddyPress.org

Changeset 6285


Ignore:
Timestamp:
09/03/2012 12:33:13 AM (9 years ago)
Author:
johnjamesjacoby
Message:

Theme Compatibility:

  • First pass at including theme compatibility into BuddyPress.
  • Introduce dependency, theme-compatibility, template-loader files into Core component.
  • Add theme compatibility classes for components with direct template access.
  • Move actions and filters around for improved plugin dependency.
  • Remove old $bbp references.
  • Turn $bp global into byref singleton, and include methods for setting up theme compatibility. (Fixes #4470.)
  • Add is_buddypress() function to bp-core-template.php.
  • Rename incorrectly named bp_after_theme_setup sub-action.
  • See: #3741.
Location:
trunk
Files:
3 added
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/bp-activity/bp-activity-screens.php

    r6093 r6285  
    304304add_action( 'bp_notification_settings', 'bp_activity_screen_notification_settings', 1 );
    305305
    306 ?>
     306/** Theme Compatability *******************************************************/
     307
     308/**
     309 * The main theme compat class for BuddyPress Activity
     310 *
     311 * This class sets up the necessary theme compatability actions to safely output
     312 * group template parts to the_title and the_content areas of a theme.
     313 *
     314 * @since BuddyPress (1.7)
     315 */
     316class BP_Activity_Theme_Compat {
     317
     318    /**
     319     * Setup the activity component theme compatibility
     320     *
     321     * @since BuddyPress (1.7)
     322     */
     323    public function __construct() {
     324        add_action( 'bp_setup_theme_compat', array( $this, 'is_activity' ) );
     325    }
     326
     327    /**
     328     * Are we looking at something that needs activity theme compatability?
     329     *
     330     * @since BuddyPress (1.7)
     331     */
     332    public function is_activity() {
     333
     334        // Bail if not looking at a group
     335        if ( ! bp_is_activity_component() )
     336            return;
     337
     338        // Activity Directory
     339        if ( ! bp_displayed_user_id() && ! bp_current_action() ) {
     340            bp_update_is_directory( true, 'activity' );
     341
     342            do_action( 'bp_activity_screen_index' );
     343
     344            add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
     345            add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
     346        }
     347    }
     348
     349    /** Directory *************************************************************/
     350
     351    /**
     352     * Update the global $post with directory data
     353     *
     354     * @since BuddyPress (1.7)
     355     */
     356    public function directory_dummy_post() {
     357        bp_theme_compat_reset_post( array(
     358            'ID'             => 0,
     359            'post_title'     => __( 'Sitewide Activity', 'buddypress' ),
     360            'post_author'    => 0,
     361            'post_date'      => 0,
     362            'post_content'   => '',
     363            'post_type'      => 'bp_activity',
     364            'post_status'    => 'publish',
     365            'is_archive'     => true,
     366            'comment_status' => 'closed'
     367        ) );
     368    }
     369
     370    /**
     371     * Filter the_content with the groups index template part
     372     *
     373     * @since BuddyPress (1.7)
     374     */
     375    public function directory_content() {
     376        bp_buffer_template_part( 'activity/index' );
     377    }
     378}
     379new BP_Activity_Theme_Compat();
  • trunk/bp-blogs/bp-blogs-screens.php

    r6093 r6285  
    2121
    2222function bp_blogs_screen_create_a_blog() {
     23
    2324    if ( !is_multisite() ||  !bp_is_blogs_component() || !bp_is_current_action( 'create' ) )
    2425        return false;
     
    4445add_action( 'bp_screens', 'bp_blogs_screen_index', 2 );
    4546
    46 ?>
     47/** Theme Compatability *******************************************************/
     48
     49/**
     50 * The main theme compat class for BuddyPress Activity
     51 *
     52 * This class sets up the necessary theme compatability actions to safely output
     53 * group template parts to the_title and the_content areas of a theme.
     54 *
     55 * @since BuddyPress (1.7)
     56 */
     57class BP_Blogs_Theme_Compat {
     58
     59    /**
     60     * Setup the groups component theme compatibility
     61     *
     62     * @since BuddyPress (1.7)
     63     */
     64    public function __construct() {
     65        add_action( 'bp_setup_theme_compat', array( $this, 'is_blogs' ) );
     66    }
     67
     68    /**
     69     * Are we looking at something that needs group theme compatability?
     70     *
     71     * @since BuddyPress (1.7)
     72     */
     73    public function is_blogs() {
     74
     75        // Bail if not looking at a group
     76        if ( ! bp_is_blogs_component() )
     77            return;
     78
     79        // Blog Directory
     80        if ( is_multisite() && ! bp_current_action() ) {
     81            bp_update_is_directory( true, 'blogs' );
     82
     83            do_action( 'bp_blogs_screen_index' );
     84
     85            add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
     86            add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
     87
     88        // Create blog
     89        } elseif ( is_user_logged_in() && bp_blog_signup_enabled() ) {
     90            add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'create_dummy_post' ) );
     91            add_filter( 'bp_replace_the_content',                    array( $this, 'create_content'    ) );         
     92        }
     93    }
     94
     95    /** Directory *************************************************************/
     96
     97    /**
     98     * Update the global $post with directory data
     99     *
     100     * @since BuddyPress (1.7)
     101     */
     102    public function directory_dummy_post() {
     103
     104        // Title based on ability to create blogs
     105        if ( is_user_logged_in() && bp_blog_signup_enabled() ) {
     106            $title = __( 'Blogs', 'buddypress' ) . '&nbsp;<a class="button" href="' . trailingslashit( bp_get_root_domain() . '/' . bp_get_blogs_root_slug() . '/create' ) . '">' . __( 'Create a Blog', 'buddypress' ) . '</a>';
     107        } else {
     108            $title = __( 'Blogs', 'buddypress' );
     109        }
     110
     111        bp_theme_compat_reset_post( array(
     112            'ID'             => 0,
     113            'post_title'     => $title,
     114            'post_author'    => 0,
     115            'post_date'      => 0,
     116            'post_content'   => '',
     117            'post_type'      => 'bp_blogs',
     118            'post_status'    => 'publish',
     119            'is_archive'     => true,
     120            'comment_status' => 'closed'
     121        ) );
     122    }
     123
     124    /**
     125     * Filter the_content with the groups index template part
     126     *
     127     * @since BuddyPress (1.7)
     128     */
     129    public function directory_content() {
     130        bp_buffer_template_part( 'blogs/index' );
     131    }
     132   
     133    /** Create ****************************************************************/
     134
     135    /**
     136     * Update the global $post with create screen data
     137     *
     138     * @since BuddyPress (1.7)
     139     */
     140    public function create_dummy_post() {
     141
     142        // Title based on ability to create blogs
     143        if ( is_user_logged_in() && bp_blog_signup_enabled() ) {
     144            $title = '<a class="button" href="' . trailingslashit( bp_get_root_domain() . '/' . bp_get_blogs_root_slug() ) . '">' . __( 'Blogs', 'buddypress' ) . '</a>&nbsp;' . __( 'Create a Blog', 'buddypress' );
     145        } else {
     146            $title = __( 'Blogs', 'buddypress' );
     147        }
     148
     149        bp_theme_compat_reset_post( array(
     150            'ID'             => 0,
     151            'post_title'     => $title,
     152            'post_author'    => 0,
     153            'post_date'      => 0,
     154            'post_content'   => '',
     155            'post_type'      => 'bp_group',
     156            'post_status'    => 'publish',
     157            'is_archive'     => true,
     158            'comment_status' => 'closed'
     159        ) );
     160    }
     161
     162    /**
     163     * Filter the_content with the create screen template part
     164     *
     165     * @since BuddyPress (1.7)
     166     */
     167    public function create_content() {
     168        bp_buffer_template_part( 'blogs/create' );
     169    }
     170}
     171new BP_Blogs_Theme_Compat();
  • trunk/bp-core/bp-core-actions.php

    r6177 r6285  
    3636add_action( 'wp',                      'bp_ready',                  10    );
    3737add_action( 'setup_theme',             'bp_setup_theme',            10    );
    38 add_action( 'after_theme_setup',       'bp_after_theme_setup',      10    );
     38add_action( 'after_setup_theme',       'bp_after_setup_theme',      10    );
    3939add_action( 'wp_enqueue_scripts',      'bp_enqueue_scripts',        10    );
    4040add_action( 'admin_bar_menu',          'bp_setup_admin_bar',        20    ); // After WP core
     
    4949 * Attach various loader actions to the bp_loaded action.
    5050 * The load order helps to execute code at the correct time.
    51  *                                                 v---Load order
     51 *                                                     v---Load order
    5252 */
    53 add_action( 'bp_loaded', 'bp_setup_components',    2  );
    54 add_action( 'bp_loaded', 'bp_include',             4  );
    55 add_action( 'bp_loaded', 'bp_setup_widgets',       6  );
    56 add_action( 'bp_loaded', 'bp_core_load_admin_bar', 10 );
     53add_action( 'bp_loaded', 'bp_setup_components',        2  );
     54add_action( 'bp_loaded', 'bp_include',                 4  );
     55add_action( 'bp_loaded', 'bp_setup_widgets',           6  );
     56add_action( 'bp_loaded', 'bp_core_load_admin_bar',     10 );
     57add_action( 'bp_loaded', 'bp_register_theme_packages', 16 );
    5758
    5859/**
     
    7778 * BuddyPress is a bully and overrides the existing themes output in many
    7879 * places. This won't always be this way, we promise.
    79  *                                                       v---Load order
     80 *                                                v---Load order
    8081 */
    81 add_action( 'bp_template_redirect', 'bp_redirect_canonical', 2 );
    82 add_action( 'bp_template_redirect', 'bp_actions',        4 );
    83 add_action( 'bp_template_redirect', 'bp_screens',        6 );
     82add_action( 'bp_template_redirect', 'bp_actions', 4 );
     83add_action( 'bp_template_redirect', 'bp_screens', 6 );
     84
     85/**
     86 * Add the BuddyPress functions file
     87 */
     88add_action( 'after_setup_theme', 'bp_load_theme_functions', 1 );
    8489
    8590// Load the admin
     
    8792    add_action( 'bp_loaded', 'bp_admin' );
    8893}
    89 
    90 /**
    91  * Plugin Dependency
    92  *
    93  * The purpose of the following actions is to mimic the behavior of something
    94  * called 'plugin dependency' which enables a plugin to have plugins of their
    95  * own in a safe and reliable way.
    96  *
    97  * We do this in BuddyPress by mirroring existing WordPress actions in many places
    98  * allowing dependant plugins to hook into the BuddyPress specific ones, thus
    99  * guaranteeing proper code execution only whenBuddyPresss is active.
    100  *
    101  * The following functions are wrappers for their actions, allowing them to be
    102  * manually called and/or piggy-backed on top of other actions if needed.
    103  */
    104 
    105 /** Sub-actions ***************************************************************/
    106 
    107 /**
    108  * Include files on this action
    109  */
    110 function bp_include() {
    111     do_action( 'bp_include' );
    112 }
    113 
    114 /**
    115  * Include files on this action
    116  */
    117 function bp_setup_components() {
    118     do_action( 'bp_setup_components' );
    119 }
    120 
    121 /**
    122  * Setup global variables and objects
    123  */
    124 function bp_setup_globals() {
    125     do_action( 'bp_setup_globals' );
    126 }
    127 
    128 /**
    129  * Set navigation elements
    130  */
    131 function bp_setup_nav() {
    132     do_action( 'bp_setup_nav' );
    133 }
    134 
    135 /**
    136  * Set up BuddyPress implementation of the WP Toolbar
    137  */
    138 function bp_setup_admin_bar() {
    139     if ( bp_use_wp_admin_bar() )
    140         do_action( 'bp_setup_admin_bar' );
    141 }
    142 
    143 /**
    144  * Set the page title
    145  */
    146 function bp_setup_title() {
    147     do_action( 'bp_setup_title' );
    148 }
    149 
    150 /**
    151  * Register widgets
    152  */
    153 function bp_setup_widgets() {
    154     do_action( 'bp_register_widgets' );
    155 }
    156 
    157 /**
    158  * Initlialize code
    159  */
    160 function bp_init() {
    161     do_action( 'bp_init' );
    162 }
    163 
    164 /**
    165  * Attached to plugins_loaded
    166  */
    167 function bp_loaded() {
    168     do_action( 'bp_loaded' );
    169 }
    170 
    171 /**
    172  * Attached to wp
    173  */
    174 function bp_ready() {
    175     do_action( 'bp_ready' );
    176 }
    177 
    178 /**
    179  * Attach potential template actions
    180  */
    181 function bp_actions() {
    182     do_action( 'bp_actions' );
    183 }
    184 
    185 /**
    186  * Attach potential template screens
    187  */
    188 function bp_screens() {
    189     do_action( 'bp_screens' );
    190 }
    191 
    192 /**
    193  * Initialize widgets
    194  */
    195 function bp_widgets_init() {
    196     do_action ( 'bp_widgets_init' );
    197 }
    198 
    199 /** Theme *********************************************************************/
    200 
    201 /**
    202  * Enqueue BuddyPress specific CSS and JS
    203  *
    204  * @since BuddyPress (1.6)
    205  *
    206  * @uses do_action() Calls 'bp_enqueue_scripts'
    207  */
    208 function bp_enqueue_scripts() {
    209     do_action ( 'bp_enqueue_scripts' );
    210 }
    211 
    212 /**
    213  * Piggy back action for BuddyPress sepecific theme actions before the theme has
    214  * been setup and the theme's functions.php has loaded.
    215  *
    216  * @since BuddyPress (1.6)
    217  *
    218  * @uses do_action() Calls 'bp_setup_theme'
    219  */
    220 function bp_setup_theme() {
    221     do_action ( 'bp_setup_theme' );
    222 }
    223 
    224 /**
    225  * Piggy back action for BuddyPress sepecific theme actions once the theme has
    226  * been setup and the theme's functions.php has loaded.
    227  *
    228  * @since BuddyPress (1.6)
    229  *
    230  * @uses do_action() Calls 'bp_after_theme_setup'
    231  */
    232 function bp_after_theme_setup() {
    233     do_action ( 'bp_after_theme_setup' );
    234 }
    235 
    236 /** Theme Compatibility Filter ************************************************/
    237 
    238 /**
    239  * The main filter used for theme compatibility and displaying custom BuddyPress
    240  * theme files.
    241  *
    242  * @since BuddyPress (1.6)
    243  *
    244  * @uses apply_filters()
    245  *
    246  * @param string $template
    247  * @return string Template file to use
    248  */
    249 function bp_template_include( $template = '' ) {
    250     return apply_filters( 'bp_template_include', $template );
    251 }
    252 
    253 /** Theme Permissions *********************************************************/
    254 
    255 /**
    256  * The main action used for redirecting BuddyPress theme actions that are not
    257  * permitted by the current_user
    258  *
    259  * @since BuddyPress (1.6)
    260  *
    261  * @uses do_action()
    262  */
    263 function bp_template_redirect() {
    264     do_action( 'bp_template_redirect' );
    265 }
    266 
    267 ?>
  • trunk/bp-core/bp-core-admin.php

    r6264 r6285  
    6666     * @since BuddyPress (1.6)
    6767     *
    68      * @uses BBP_Admin::setup_globals() Setup the globals needed
    69      * @uses BBP_Admin::includes() Include the required files
    70      * @uses BBP_Admin::setup_actions() Setup the hooks and actions
     68     * @uses BP_Admin::setup_globals() Setup the globals needed
     69     * @uses BP_Admin::includes() Include the required files
     70     * @uses BP_Admin::setup_actions() Setup the hooks and actions
    7171     */
    7272    public function __construct() {
     
    219219                'bp-general-settings',
    220220                'bp_core_admin_backpat_menu',
    221                 ''
     221                'div'
    222222            );
    223223
  • trunk/bp-core/bp-core-caps.php

    r6185 r6285  
    231231 * @since BuddyPress (1.6)
    232232 *
    233  * @global BuddyPress $bbp
     233 * @global BuddyPress $bp
    234234 *
    235235 * @uses is_multisite()
     
    258258    // Give the user the 'Forum Participant' role
    259259    if ( current_user_can( 'bp_masked' ) ) {
    260         global $bbp;
     260        global $bp;
    261261
    262262        // Get the default role
     
    264264
    265265        // Set the current users default role
    266         $bbp->current_user->set_role( $default_role );
     266        $bp->current_user->set_role( $default_role );
    267267    }
    268268}
     
    323323 * @uses bp_get_caps_for_role()
    324324 *
    325  * @global BuddyPress $bbp
     325 * @global BuddyPress $bp
    326326 * @return If not multisite, not global, or user is deleted/spammed
    327327 */
  • trunk/bp-core/bp-core-catchuri.php

    r6259 r6285  
    370370    }
    371371
    372     // Define local variables
    373     $located_template   = false;
     372    // Fetch each template and add the php suffix
    374373    $filtered_templates = array();
    375 
    376     // Fetch each template and add the php suffix
    377     foreach ( (array) $templates as $template )
     374    foreach ( (array) $templates as $template ) {
    378375        $filtered_templates[] = $template . '.php';
     376    }
    379377
    380378    // Filter the template locations so that plugins can alter where they are located
     
    392390
    393391        do_action( 'bp_core_post_load_template', $located_template );
    394     }
    395 
    396     // Kill any other output after this.
    397     die;
     392
     393        // Kill any other output after this.
     394        exit();
     395
     396    // No template found, so setup theme compatability
     397    // @todo Some other 404 handling if theme compat doesn't kick in
     398    } else {
     399        do_action( 'bp_setup_theme_compat' );
     400    }
    398401}
    399402
  • trunk/bp-core/bp-core-filters.php

    r6034 r6285  
    11<?php
     2
     3/**
     4 * BuddyPress Filters
     5 *
     6 * @package BuddyPress
     7 * @subpackage Core
     8 *
     9 * This file contains the filters that are used through-out BuddyPress. They are
     10 * consolidated here to make searching for them easier, and to help developers
     11 * understand at a glance the order in which things occur.
     12 *
     13 * There are a few common places that additional filters can currently be found
     14 *
     15 *  - BuddyPress: In {@link BuddyPress::setup_actions()} in buddypress.php
     16 *  - Component: In {@link BP_Component::setup_actions()} in
     17 *                bp-core/bp-core-component.php
     18 *  - Admin: More in {@link BP_Admin::setup_actions()} in
     19 *            bp-core/bp-core-admin.php
     20 *
     21 * @see bp-core-actions.php
     22 */
    223
    324// Exit if accessed directly
    425if ( !defined( 'ABSPATH' ) ) exit;
     26
     27/**
     28 * Attach BuddyPress to WordPress
     29 *
     30 * BuddyPress uses its own internal actions to help aid in third-party plugin
     31 * development, and to limit the amount of potential future code changes when
     32 * updates to WordPress core occur.
     33 *
     34 * These actions exist to create the concept of 'plugin dependencies'. They
     35 * provide a safe way for plugins to execute code *only* when BuddyPress is
     36 * installed and activated, without needing to do complicated guesswork.
     37 *
     38 * For more information on how this works, see the 'Plugin Dependency' section
     39 * near the bottom of this file.
     40 *
     41 *           v--WordPress Actions       v--BuddyPress Sub-actions
     42 */
     43add_filter( 'request',                 'bp_request',             10    );
     44add_filter( 'template_include',        'bp_template_include',    10    );
     45add_filter( 'login_redirect',          'bp_login_redirect',      10, 3 );
    546
    647// Add some filters to feedback messages
     
    1051add_filter( 'bp_core_render_message_content', 'wpautop'           );
    1152add_filter( 'bp_core_render_message_content', 'shortcode_unautop' );
     53
     54/**
     55 * Template Compatibility
     56 *
     57 * If you want to completely bypass this and manage your own custom BuddyPress
     58 * template hierarchy, start here by removing this filter, then look at how
     59 * bp_template_include() works and do something similar. :)
     60 */
     61add_filter( 'bp_template_include', 'bp_template_include_theme_supports', 2, 1 );
     62add_filter( 'bp_template_include', 'bp_template_include_theme_compat',   4, 2 );
     63
     64// Run all template parts through additional template locations
     65add_filter( 'bp_get_template_part', 'bp_add_template_locations' );
     66
     67// Turn comments off for BuddyPress pages
     68add_filter( 'comments_open', 'bp_comments_open', 10, 2 );
    1269
    1370/**
     
    157214    return bp_get_root_domain();
    158215}
    159 add_filter( 'login_redirect', 'bp_core_login_redirect', 10, 3 );
     216add_filter( 'bp_login_redirect', 'bp_core_login_redirect', 10, 3 );
    160217
    161218/***
     
    358415add_filter( 'bp_modify_page_title', 'convert_chars'   );
    359416add_filter( 'bp_modify_page_title', 'esc_html'        );
    360 
    361 ?>
  • trunk/bp-core/bp-core-options.php

    r6183 r6285  
    6161        // Allow comments on blog and forum activity items
    6262        'bp-disable-blogforum-comments'   => true,
     63
     64        // The ID for the current theme package.
     65        '_bp_theme_package_id'            => 'legacy',
    6366
    6467        /** Groups ************************************************************/
     
    522525}
    523526
    524 ?>
     527/**
     528 * Get the current theme package ID
     529 *
     530 * @since BuddyPress (1.7)
     531 *
     532 * @param $default string Optional. Default value 'default'
     533 * @uses get_option() To get the subtheme option
     534 * @return string ID of the subtheme
     535 */
     536function bp_get_theme_package_id( $default = 'legacy' ) {
     537    return apply_filters( 'bp_get_theme_package_id', get_option( '_bp_theme_package_id', $default ) );
     538}
  • trunk/bp-core/bp-core-template.php

    r6259 r6285  
    10561056}
    10571057
     1058/**
     1059 * Is this a BuddyPress component?
     1060 *
     1061 * You can tell if a page is displaying BP content by whether the
     1062 * current_component has been defined
     1063 *
     1064 * Generally, we can just check to see that there's no current component.
     1065 * The one exception is single user home tabs, where $bp->current_component
     1066 * is unset. Thus the addition of the bp_is_user() check.
     1067 *
     1068 * @since BuddyPress (1.7)
     1069 *
     1070 * @package BuddyPress
     1071 * @return bool True if it's a BuddyPress page, false otherwise
     1072 */
     1073function is_buddypress() {
     1074    $retval = (bool) ( bp_current_component() || bp_is_user() );
     1075
     1076    return apply_filters( 'is_buddypress', $retval );
     1077}
     1078
    10581079/** Components ****************************************************************/
    10591080
  • trunk/bp-groups/bp-groups-filters.php

    r5927 r6285  
    1010// Exit if accessed directly
    1111if ( !defined( 'ABSPATH' ) ) exit;
     12
     13// Filter bbPress template locations
     14
     15add_filter( 'bp_groups_get_directory_template', 'bp_add_template_locations' );
     16add_filter( 'bp_get_single_group_template',    'bp_add_template_locations' );
    1217
    1318/* Apply WordPress defined filters */
  • trunk/bp-groups/bp-groups-screens.php

    r6093 r6285  
    149149
    150150                $topic_page = isset( $_GET['topic_page'] ) ? $_GET['topic_page'] : false;
    151                
     151
    152152                // Don't allow reply flooding
    153153                if ( bp_forums_reply_exists( $_POST['reply_text'], $topic_id, bp_loggedin_user_id() ) ) {
    154154                    bp_core_add_message( __( 'It looks like you\'ve already said that!', 'buddypress' ), 'error' );
    155                 } else {   
     155                } else {
    156156                    if ( !$post_id = groups_new_group_forum_post( $_POST['reply_text'], $topic_id, $topic_page ) )
    157157                        bp_core_add_message( __( 'There was an error when replying to that topic', 'buddypress'), 'error' );
     
    161161
    162162                $query_vars = isset( $_SERVER['QUERY_STRING'] ) ? '?' . $_SERVER['QUERY_STRING'] : '';
    163                
     163
    164164                $redirect = bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic_slug . '/' . $query_vars;
    165165
     
    883883add_action( 'bp_notification_settings', 'groups_screen_notification_settings' );
    884884
    885 ?>
     885/** Theme Compatability *******************************************************/
     886
     887/**
     888 * The main theme compat class for BuddyPress Groups
     889 *
     890 * This class sets up the necessary theme compatability actions to safely output
     891 * group template parts to the_title and the_content areas of a theme.
     892 *
     893 * @since BuddyPress (1.7)
     894 */
     895class BP_Groups_Theme_Compat {
     896
     897    /**
     898     * Setup the groups component theme compatibility
     899     *
     900     * @since BuddyPress (1.7)
     901     */
     902    public function __construct() {
     903        add_action( 'bp_setup_theme_compat', array( $this, 'is_group' ) );
     904    }
     905
     906    /**
     907     * Are we looking at something that needs group theme compatability?
     908     *
     909     * @since BuddyPress (1.7)
     910     */
     911    public function is_group() {
     912
     913        // Bail if not looking at a group
     914        if ( ! bp_is_groups_component() )
     915            return;
     916
     917        // Group Directory
     918        if ( ! bp_current_action() && ! bp_current_item() ) {
     919            bp_update_is_directory( true, 'groups' );
     920
     921            do_action( 'groups_directory_groups_setup' );
     922
     923            add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
     924            add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
     925
     926        // Creating a group
     927        } elseif ( bp_is_groups_component() && bp_is_current_action( 'create' ) ) {
     928            add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'create_dummy_post' ) );
     929            add_filter( 'bp_replace_the_content',                    array( $this, 'create_content'    ) );
     930
     931        // Group admin
     932        } elseif ( bp_is_single_item() ) {
     933            add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'single_dummy_post' ) );
     934            add_filter( 'bp_replace_the_content',                    array( $this, 'single_content'    ) );
     935
     936        }
     937    }
     938
     939    /** Directory *************************************************************/
     940
     941    /**
     942     * Update the global $post with directory data
     943     *
     944     * @since BuddyPress (1.7)
     945     */
     946    public function directory_dummy_post() {
     947
     948        // Title based on ability to create groups
     949        if ( is_user_logged_in() && bp_user_can_create_groups() ) {
     950            $title = __( 'Groups', 'buddypress' ) . '&nbsp;<a class="button" href="' . trailingslashit( bp_get_root_domain() . '/' . bp_get_groups_root_slug() . '/create' ) . '">' . __( 'Create a Group', 'buddypress' ) . '</a>';
     951        } else {
     952            $title = __( 'Groups', 'buddypress' );
     953        }
     954
     955        bp_theme_compat_reset_post( array(
     956            'ID'             => 0,
     957            'post_title'     => $title,
     958            'post_author'    => 0,
     959            'post_date'      => 0,
     960            'post_content'   => '',
     961            'post_type'      => 'bp_group',
     962            'post_status'    => 'publish',
     963            'is_archive'     => true,
     964            'comment_status' => 'closed'
     965        ) );
     966    }
     967
     968    /**
     969     * Filter the_content with the groups index template part
     970     *
     971     * @since BuddyPress (1.7)
     972     */
     973    public function directory_content() {
     974        bp_buffer_template_part( 'groups/index' );
     975    }
     976
     977    /** Create ****************************************************************/
     978
     979    /**
     980     * Update the global $post with create screen data
     981     *
     982     * @since BuddyPress (1.7)
     983     */
     984    public function create_dummy_post() {
     985
     986        // Title based on ability to create groups
     987        if ( is_user_logged_in() && bp_user_can_create_groups() ) {
     988            $title = '<a class="button" href="' . trailingslashit( bp_get_root_domain() . '/' . bp_get_groups_root_slug() ) . '">' . __( 'Groups', 'buddypress' ) . '</a>&nbsp;' . __( 'Create a Group', 'buddypress' );
     989        } else {
     990            $title = __( 'Groups', 'buddypress' );
     991        }
     992
     993        bp_theme_compat_reset_post( array(
     994            'ID'             => 0,
     995            'post_title'     => $title,
     996            'post_author'    => 0,
     997            'post_date'      => 0,
     998            'post_content'   => '',
     999            'post_type'      => 'bp_group',
     1000            'post_status'    => 'publish',
     1001            'is_archive'     => true,
     1002            'comment_status' => 'closed'
     1003        ) );
     1004    }
     1005
     1006    /**
     1007     * Filter the_content with the create screen template part
     1008     *
     1009     * @since BuddyPress (1.7)
     1010     */
     1011    public function create_content() {
     1012        bp_buffer_template_part( 'groups/create' );
     1013    }
     1014
     1015    /** Single ****************************************************************/
     1016
     1017    /**
     1018     * Update the global $post with single group data
     1019     *
     1020     * @since BuddyPress (1.7)
     1021     */
     1022    public function single_dummy_post() {
     1023        bp_theme_compat_reset_post( array(
     1024            'ID'             => 0,
     1025            'post_title'     => bp_get_current_group_name(),
     1026            'post_author'    => 0,
     1027            'post_date'      => 0,
     1028            'post_content'   => '',
     1029            'post_type'      => 'bp_group',
     1030            'post_status'    => 'publish',
     1031            'is_archive'     => true,
     1032            'comment_status' => 'closed'
     1033        ) );
     1034    }
     1035
     1036    /**
     1037     * Filter the_content with the single group template part
     1038     *
     1039     * @since BuddyPress (1.7)
     1040     */
     1041    public function single_content() {
     1042        bp_buffer_template_part( 'groups/single/home' );
     1043    }
     1044}
     1045new BP_Groups_Theme_Compat();
  • trunk/bp-loader.php

    r6275 r6285  
    3535class BuddyPress {
    3636
    37     /**
    38      * Note to Plugin and Theme authors:
    39      *
    40      * Do not directly reference the variables below in your code. Their names
    41      * and locations in the BuddyPress class are subject to change at any time.
    42      *
    43      * Most of them have reference functions located in bp-core-functions.php.
    44      * The ones that don't can be accessed via their respective WordPress API's.
    45      *
    46      * Components are encouraged to store their data in the $bp global rather
    47      * than new globals to keep all BuddyPress data in one place.
    48      */
    49 
    50     /** Version ***************************************************************/
    51 
    52     /**
    53      * @var string BuddyPress version
    54      */
    55     public $version = '1.7-bleeding-6275';
    56 
    57     /**
    58      * @var int Database version of current BuddyPress files
    59      */
    60     public $db_version = 6066;
    61 
    62     /**
    63      * @var int Database version raw from database connection
    64      */
    65     public $db_version_raw = 0;
    66 
    67     /**
    68      * @var string State of BuddyPress installation
    69      */
    70     public $maintenance_mode = '';
    71 
    72     /**
    73      * @var bool Include deprecated BuddyPress files or not
    74      */
    75     public $load_deprecated = true;
    76 
    77     /** Root ******************************************************************/
    78 
    79     /**
    80      * @var int The root blog ID
    81      */
    82     public $root_blog_id = 1;
    83 
    84     /** Paths *****************************************************************/
    85 
    86     /**
    87      * The absolute path and filename of this file.
    88      *
    89      * @since BuddyPress (1.6)
    90      * @var string
    91      */
    92     public $file;
    93 
    94     /**
    95      * @var string Basename of the BuddyPress plugin directory
    96      */
    97     public $basename = '';
    98 
    99     /**
    100      * @var string Absolute path to the BuddyPress plugin directory
    101      */
    102     public $plugin_dir = '';
    103 
    104     /**
    105      * @var string Absolute path to the BuddyPress themes directory
    106      */
    107     public $themes_dir = '';
    108 
    109     /**
    110      * @var string Absolute path to the BuddyPress language directory
    111      */
    112     public $lang_dir = '';
    113 
    114     /** URLs ******************************************************************/
    115 
    116     /**
    117      * @var string URL to the BuddyPress plugin directory
    118      */
    119     public $plugin_url = '';
    120 
    121     /**
    122      * @var string URL to the BuddyPress themes directory
    123      */
    124     public $themes_url = '';
    125 
    126     /** Users *****************************************************************/
    127 
    128     /**
    129      * @var object Current user
    130      */
    131     public $current_user = null;
    132 
    133     /**
    134      * @var object Displayed user
    135      */
    136     public $displayed_user = null;
    137 
    138     /** Navigation ************************************************************/
     37    /** Magic *****************************************************************/
     38
     39    /**
     40     * BuddyPress uses many variables, most of which can be filtered to customize
     41     * the way that it works. To prevent unauthorized access, these variables
     42     * are stored in a private array that is magically updated using PHP 5.2+
     43     * methods. This is to prevent third party plugins from tampering with
     44     * essential information indirectly, which would cause issues later.
     45     *
     46     * @see BuddyPress::setup_globals()
     47     * @var array
     48     */
     49    private $data;
     50
     51    /** Not Magic *************************************************************/
    13952
    14053    /**
     
    14760     */
    14861    public $bp_options_nav = array();
    149 
    150     /** Toolbar ***************************************************************/
    151 
    152     /**
    153      * @var string The primary toolbar ID
    154      */
    155     public $my_account_menu_id = '';
    156 
    157     /** URI's *****************************************************************/
    15862
    15963    /**
     
    16266     */
    16367    public $unfiltered_uri = array();
    164 
    165     /**
    166      * @var int The current offset of the URI
    167      * @see bp_core_set_uri_globals()
    168      */
    169     public $unfiltered_uri_offset = 0;
    170 
    171     /**
    172      * @var bool Are status headers already sent?
    173      */
    174     public $no_status_set = false;
    17568
    17669    /**
     
    18174    public $canonical_stack = array();
    18275
    183     /** Components ************************************************************/
    184 
    185     /**
    186      * @var string Name of the current BuddyPress component (primary)
    187      */
    188     public $current_component = '';
    189 
    190     /**
    191      * @var string Name of the current BuddyPress item (secondary)
    192      */
    193     public $current_item = '';
    194 
    195     /**
    196      * @var string Name of the current BuddyPress action (tertiary)
    197      */
    198     public $current_action = '';
    199 
    20076    /**
    20177     * @var array Additional navigation elements (supplemental)
     
    20480
    20581    /**
    206      * @var bool Displaying custom 2nd level navigation menu (I.E a group)
    207      */
    208     public $is_single_item = false;
     82     * @var array Required components (core, members)
     83     */
     84    public $required_components = array();
     85
     86    /**
     87     * @var array Additional active components
     88     */
     89    public $loaded_components = array();
    20990
    21091    /** Option Overload *******************************************************/
     
    21596    public $options = array();
    21697
    217     /** Functions *************************************************************/
    218 
    219     /**
    220      * The main BuddyPress loader
    221      *
    222      * @since BuddyPress (1.6)
    223      *
    224      * @uses BuddyPress::constants() Setup legacy constants
    225      * @uses BuddyPress::setup_globals() Setup globals needed
    226      * @uses BuddyPress::includes() Includ required files
    227      * @uses BuddyPress::setup_actions() Setup hooks and actions
    228      */
    229     public function __construct() {
    230         $this->constants();
    231         $this->setup_globals();
    232         $this->includes();
    233         $this->setup_actions();
    234     }
     98    /** Singleton *************************************************************/
     99
     100    /**
     101     * @var BuddyPress The one true BuddyPress
     102     */
     103    private static $instance;
     104
     105    /**
     106     * Main BuddyPress Instance
     107     *
     108     * BuddyPress is great
     109     * Please load it only one time
     110     * For this, we thank you
     111     *
     112     * Insures that only one instance of BuddyPress exists in memory at any one
     113     * time. Also prevents needing to define globals all over the place.
     114     *
     115     * @since BuddyPress (1.7)
     116     *
     117     * @staticvar array $instance
     118     * @uses BuddyPress::constants() Setup the constants (mostly deprecated)
     119     * @uses BuddyPress::setup_globals() Setup the globals needed
     120     * @uses BuddyPress::includes() Include the required files
     121     * @uses BuddyPress::setup_actions() Setup the hooks and actions
     122     * @see buddypress()
     123     *
     124     * @return The one true BuddyPress
     125     */
     126    public static function instance() {
     127        if ( ! isset( self::$instance ) ) {
     128            self::$instance = new BuddyPress;
     129            self::$instance->constants();
     130            self::$instance->setup_globals();
     131            self::$instance->includes();
     132            self::$instance->setup_actions();
     133        }
     134        return self::$instance;
     135    }
     136
     137    /** Magic Methods *********************************************************/
     138
     139    /**
     140     * A dummy constructor to prevent BuddyPress from being loaded more than once.
     141     *
     142     * @since BuddyPress (1.7)
     143     * @see BuddyPress::instance()
     144     * @see buddypress()
     145     */
     146    private function __construct() { /* Do nothing here */ }
     147
     148    /**
     149     * A dummy magic method to prevent BuddyPress from being cloned
     150     *
     151     * @since BuddyPress (1.7)
     152     */
     153    public function __clone() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'buddypress' ), '1.7' ); }
     154
     155    /**
     156     * A dummy magic method to prevent BuddyPress from being unserialized
     157     *
     158     * @since BuddyPress (1.7)
     159     */
     160    public function __wakeup() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'buddypress' ), '1.7' ); }
     161
     162    /**
     163     * Magic method for checking the existence of a certain custom field
     164     *
     165     * @since BuddyPress (1.7)
     166     */
     167    public function __isset( $key ) { return isset( $this->data[$key] ); }
     168
     169    /**
     170     * Magic method for getting BuddyPress varibles
     171     *
     172     * @since BuddyPress (1.7)
     173     */
     174    public function __get( $key ) { return isset( $this->data[$key] ) ? $this->data[$key] : null; }
     175
     176    /**
     177     * Magic method for setting BuddyPress varibles
     178     *
     179     * @since BuddyPress (1.7)
     180     */
     181    public function __set( $key, $value ) { $this->data[$key] = $value; }
     182
     183    /** Private Methods *******************************************************/
    235184
    236185    /**
     
    316265    private function setup_globals() {
    317266
     267        /** Versions **********************************************************/
     268
     269        $this->version        = '1.7-bleeding-6275';
     270        $this->db_version     = 6066;
     271
     272        /** Loading ***********************************************************/
     273
     274        $this->maintenance_mode = '';
     275        $this->load_deprecated  = true;
     276
     277        /** Toolbar ***********************************************************/
     278
     279        /**
     280         * @var string The primary toolbar ID
     281         */
     282        $this->my_account_menu_id = '';
     283
     284        /** URI's *************************************************************/
     285
     286        /**
     287         * @var int The current offset of the URI
     288         * @see bp_core_set_uri_globals()
     289         */
     290        $this->unfiltered_uri_offset = 0;
     291
     292        /**
     293         * @var bool Are status headers already sent?
     294         */
     295        $this->no_status_set = false;
     296
     297        /** Components ********************************************************/
     298
     299        /**
     300         * @var string Name of the current BuddyPress component (primary)
     301         */
     302        $this->current_component = '';
     303
     304        /**
     305         * @var string Name of the current BuddyPress item (secondary)
     306         */
     307        $this->current_item = '';
     308
     309        /**
     310         * @var string Name of the current BuddyPress action (tertiary)
     311         */
     312        $this->current_action = '';
     313
     314        /**
     315         * @var bool Displaying custom 2nd level navigation menu (I.E a group)
     316         */
     317        $this->is_single_item = false;
     318   
    318319        /** Root **************************************************************/
    319320
     
    336337        $this->lang_dir   = $this->plugin_dir . 'bp-languages';
    337338
     339        /** Theme Compat ******************************************************/
     340
     341        $this->theme_compat   = new stdClass(); // Base theme compatibility class
     342        $this->filters        = new stdClass(); // Used when adding/removing filters
     343
    338344        /** Users *************************************************************/
    339345
     
    368374            $versions['1.5-multi']  = get_site_option(                           'bp-db-version' );
    369375            $versions['1.6-multi']  = get_site_option(                          '_bp_db_version' );
    370             $versions['1.5-single'] = get_blog_option( $this->root_blog_id,     'bp-db-version' );
     376            $versions['1.5-single'] = get_blog_option( $this->root_blog_id,      'bp-db-version' );
    371377
    372378            // Remove empty array items
     
    412418        if ( empty( $this->maintenance_mode ) ) {
    413419
     420            // Theme compatability
     421            require( $this->plugin_dir . 'bp-core/bp-core-template-loader.php'     );
     422            require( $this->plugin_dir . 'bp-core/bp-core-theme-compatibility.php' );
     423
    414424            // Require all of the BuddyPress core libraries
     425            require( $this->plugin_dir . 'bp-core/bp-core-dependency.php' );
    415426            require( $this->plugin_dir . 'bp-core/bp-core-actions.php'    );
    416427            require( $this->plugin_dir . 'bp-core/bp-core-caps.php'       );
     
    462473        // Array of BuddyPress core actions
    463474        $actions = array(
     475            'setup_theme',              // Setup the default theme compat
    464476            'setup_current_user',       // Setup currently logged in user
    465477            'register_post_types',      // Register post types
     
    468480            'register_views',           // Register the views
    469481            'register_theme_directory', // Register the theme directory
     482            'register_theme_packages',  // Register bundled theme packages (bp-themes)
    470483            'load_textdomain',          // Load textdomain
    471484            'add_rewrite_tags',         // Add rewrite tags
     
    480493        register_theme_directory( $this->themes_dir );
    481494    }
     495   
     496    /** Public Methods ********************************************************/
     497
     498    /**
     499     * Register bundled theme packages
     500     *
     501     * Note that since we currently have complete control over bp-themes and
     502     * the bp-legacy folders, it's fine to hardcode these here. If at a
     503     * later date we need to automate this, an API will need to be built.
     504     *
     505     * @since BuddyPress (1.7)
     506     */
     507    public function register_theme_packages() {
     508        bp_register_theme_package( array(
     509            'id'      => 'legacy',
     510            'name'    => __( 'BuddyPress Default', 'buddypress' ),
     511            'version' => bp_get_version(),
     512            'dir'     => trailingslashit( $this->themes_dir . '/bp-legacy' ),
     513            'url'     => trailingslashit( $this->themes_url . '/bp-legacy' )
     514        ) );
     515    }
     516   
     517    /**
     518     * Setup the default BuddyPress theme compatability location.
     519     *
     520     * @since BuddyPress (1.7)
     521     */
     522    public function setup_theme() {
     523
     524        // Bail if something already has this under control
     525        if ( ! empty( $this->theme_compat->theme ) )
     526            return;
     527
     528        // Setup the theme package to use for compatibility
     529        bp_setup_theme_compat( bp_get_theme_package_id() );
     530    }
    482531}
    483532
    484 // "And now for something completely different"
    485 $GLOBALS['bp'] = new BuddyPress;
     533/**
     534 * The main function responsible for returning the one true BuddyPress Instance
     535 * to functions everywhere.
     536 *
     537 * Use this function like you would a global variable, except without needing
     538 * to declare the global.
     539 *
     540 * Example: <?php $bp = buddypress(); ?>
     541 *
     542 * @return The one true BuddyPress Instance
     543 */
     544function buddypress() {
     545    return buddypress::instance();
     546}
     547
     548/**
     549 * Hook BuddyPress early onto the 'plugins_loaded' action.
     550 *
     551 * This gives all other plugins the chance to load before BuddyPress, to get
     552 * their actions, filters, and overrides setup without BuddyPress being in the
     553 * way.
     554 */
     555if ( defined( 'BUDDYPRESS_LATE_LOAD' ) ) {
     556    add_action( 'plugins_loaded', 'buddypress', (int) BUDDYPRESS_LATE_LOAD );
     557
     558// "And now here's something we hope you'll really like!"
     559} else {
     560    $GLOBALS['bp'] = &buddypress();
     561}
    486562
    487563endif;
    488 
    489 ?>
  • trunk/bp-members/bp-members-screens.php

    r6236 r6285  
    261261add_action( 'bp_screens', 'bp_core_screen_activation' );
    262262
    263 ?>
     263/** Theme Compatability *******************************************************/
     264
     265/**
     266 * The main theme compat class for BuddyPress Groups
     267 *
     268 * This class sets up the necessary theme compatability actions to safely output
     269 * group template parts to the_title and the_content areas of a theme.
     270 *
     271 * @since BuddyPress (1.7)
     272 */
     273class BP_Members_Theme_Compat {
     274
     275    /**
     276     * Setup the groups component theme compatibility
     277     *
     278     * @since BuddyPress (1.7)
     279     */
     280    public function __construct() {
     281        add_action( 'bp_setup_theme_compat', array( $this, 'is_members' ) );
     282    }
     283
     284    /**
     285     * Are we looking at something that needs group theme compatability?
     286     *
     287     * @since BuddyPress (1.7)
     288     */
     289    public function is_members() {
     290
     291        // Bail if not looking at a group
     292        if ( ! bp_is_members_component() )
     293            return;
     294
     295        // Group Directory
     296        if ( ! bp_current_action() && ! bp_current_item() ) {
     297            bp_update_is_directory( true, 'members' );
     298
     299            do_action( 'members_directory_groups_setup' );
     300
     301            add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
     302            add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
     303        }
     304    }
     305
     306    /** Directory *************************************************************/
     307
     308    /**
     309     * Update the global $post with directory data
     310     *
     311     * @since BuddyPress (1.7)
     312     */
     313    public function directory_dummy_post() {
     314        bp_theme_compat_reset_post( array(
     315            'ID'             => 0,
     316            'post_title'     => __( 'Members', 'buddypress' ),
     317            'post_author'    => 0,
     318            'post_date'      => 0,
     319            'post_content'   => '',
     320            'post_type'      => 'bp_members',
     321            'post_status'    => 'publish',
     322            'is_archive'     => true,
     323            'comment_status' => 'closed'
     324        ) );
     325    }
     326
     327    /**
     328     * Filter the_content with the groups index template part
     329     *
     330     * @since BuddyPress (1.7)
     331     */
     332    public function directory_content() {
     333        bp_buffer_template_part( 'members/index' );
     334    }
     335}
     336new BP_Members_Theme_Compat();
  • trunk/bp-themes/bp-default/_inc/ajax.php

    r6259 r6285  
    7676    }
    7777}
    78 add_action( 'after_setup_theme', 'bp_dtheme_register_actions', 20 );
     78add_action( 'bp_after_setup_theme', 'bp_dtheme_register_actions', 20 );
    7979
    8080/**
  • trunk/bp-xprofile/bp-xprofile-screens.php

    r6070 r6285  
    190190}
    191191
    192 ?>
     192/** Theme Compatability *******************************************************/
     193
     194/**
     195 * The main theme compat class for BuddyPress Profiles
     196 *
     197 * This class sets up the necessary theme compatability actions to safely output
     198 * group template parts to the_title and the_content areas of a theme.
     199 *
     200 * @since BuddyPress (1.7)
     201 */
     202class BP_XProfile_Theme_Compat {
     203
     204    /**
     205     * Setup the xprofile component theme compatibility
     206     *
     207     * @since BuddyPress (1.7)
     208     *
     209     * @todo is 'bp_screens' correct here?
     210     */
     211    public function __construct() {
     212        add_action( 'bp_setup_theme_compat', array( $this, 'is_xprofile' ) );
     213    }
     214
     215    /**
     216     * Are we looking at something that needs group theme compatability?
     217     *
     218     * @since BuddyPress (1.7)
     219     */
     220    public function is_xprofile() {
     221
     222        // Bail if not looking at a profile
     223        if ( ! bp_displayed_user_id() )
     224            return;
     225
     226        // Creating a group
     227        add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'dummy_post'    ) );
     228        add_filter( 'bp_replace_the_content',                    array( $this, 'dummy_content' ) );
     229    }
     230
     231    /** Directory *************************************************************/
     232
     233    /**
     234     * Update the global $post with directory data
     235     *
     236     * @since BuddyPress (1.7)
     237     */
     238    public function dummy_post() {
     239        bp_theme_compat_reset_post( array(
     240            'ID'             => 0,
     241            'post_title'     => bp_get_displayed_user_fullname(),
     242            'post_author'    => 0,
     243            'post_date'      => 0,
     244            'post_content'   => '',
     245            'post_type'      => 'bp_xprofile',
     246            'post_status'    => 'publish',
     247            'is_archive'     => true,
     248            'comment_status' => 'closed'
     249        ) );
     250    }
     251
     252    /**
     253     * Filter the_content with the groups index template part
     254     *
     255     * @since BuddyPress (1.7)
     256     */
     257    public function dummy_content() {
     258        bp_buffer_template_part( 'members/single/home' );
     259    }
     260}
     261new BP_XProfile_Theme_Compat();
Note: See TracChangeset for help on using the changeset viewer.