Skip to:
Content

BuddyPress.org

Changeset 13342


Ignore:
Timestamp:
10/22/2022 09:45:12 AM (4 years ago)
Author:
imath
Message:

Stop using the post link into the new_blog_post activity action

Now the post link is included into the activity content (see [13305]), displaying it into the activity action is no more needed.
This commit also :

  • introduces a new function to get a site's URL and Name out of BP Blogs meta which avoids some code duplication in new_blog, new_blog_post and new_blog_comment activity action format callbacks.
  • improves the new_blog_post activity content layout when the corresponding post's image is not the featured one.
  • properly deprecates pre-2.0 activity action filters.

Props teeboy4real

Closes https://github.com/buddypress/buddypress/pull/27
Fixes #8052

Location:
trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-blogs/bp-blogs-activity.php

    r13108 r13342  
    116116
    117117/**
     118 * Returns the blog URL and name which relates to a post or comment activity.
     119 *
     120 * @since 11.0.0
     121 *
     122 * @param BP_Activity_Activity $activity The activity object.
     123 * @return array The blog URL and name which relates to a post or comment activity.
     124 */
     125function bp_blogs_activity_get_site_link_meta( $activity = null ) {
     126        $attributes = array(
     127                'blog_url'  => '',
     128                'blog_name' => '',
     129        );
     130
     131        if ( ! isset( $activity->item_id, $activity->component ) || ! $activity->item_id || 'blogs' !== $activity->component ) {
     132                return $attributes;
     133        }
     134
     135        if ( isset( $activity->blog_url ) ) {
     136                $attributes['blog_url'] = $activity->blog_url;
     137        } else {
     138                $blog_url = bp_blogs_get_blogmeta( $activity->item_id, 'url' );
     139
     140                if ( ! $blog_url ) {
     141                        $blog_url = get_home_url( $activity->item_id );
     142                        bp_blogs_update_blogmeta( $activity->item_id, 'url', $blog_url );
     143                } else {
     144                        $attributes['blog_url'] = $blog_url;
     145                }
     146        }
     147
     148        if ( isset( $activity->blog_name ) ) {
     149                $attributes['blog_name'] = $activity->blog_name;
     150        } else {
     151                $blog_name = bp_blogs_get_blogmeta( $activity->item_id, 'name' );
     152
     153                if ( ! $blog_name ) {
     154                        $blog_name = get_blog_option( $activity->item_id, 'blogname' );
     155                        bp_blogs_update_blogmeta( $activity->item_id, 'name', $blog_name );
     156                } else {
     157                        $attributes['blog_name'] = $blog_name;
     158                }
     159        }
     160
     161        return $attributes;
     162}
     163
     164/**
    118165 * Format 'new_blog' activity actions.
    119166 *
     
    122169 * @param string $action   Static activity action.
    123170 * @param object $activity Activity data object.
    124  * @return string
     171 * @return string Constructed activity action.
    125172 */
    126173function bp_blogs_format_activity_action_new_blog( $action, $activity ) {
    127         $blog_url  = bp_blogs_get_blogmeta( $activity->item_id, 'url' );
    128         $blog_name = bp_blogs_get_blogmeta( $activity->item_id, 'name' );
     174        list( $blog_url, $blog_name ) = array_values( bp_blogs_activity_get_site_link_meta( $activity ) );
    129175
    130176        $action = sprintf(
     
    143189
    144190                if ( isset( $recorded_blog ) ) {
    145                         $action = apply_filters( 'bp_blogs_activity_created_blog_action', $action, $recorded_blog, $blog_name, bp_blogs_get_blogmeta( $activity->item_id, 'description' ) );
     191                        $blog_description = bp_blogs_get_blogmeta( $activity->item_id, 'description' );
     192                        $action           = apply_filters_deprecated( 'bp_blogs_activity_created_blog_action', array( $action, $recorded_blog, $blog_name, $blog_description ), '2.0.0', 'bp_blogs_format_activity_action_new_blog' );
    146193                }
    147194        }
     
    168215 */
    169216function bp_blogs_format_activity_action_new_blog_post( $action, $activity ) {
    170         $blog_url  = bp_blogs_get_blogmeta( $activity->item_id, 'url' );
    171         $blog_name = bp_blogs_get_blogmeta( $activity->item_id, 'name' );
    172 
    173         if ( empty( $blog_url ) || empty( $blog_name ) ) {
    174                 $blog_url  = get_home_url( $activity->item_id );
    175                 $blog_name = get_blog_option( $activity->item_id, 'blogname' );
    176 
    177                 bp_blogs_update_blogmeta( $activity->item_id, 'url', $blog_url );
    178                 bp_blogs_update_blogmeta( $activity->item_id, 'name', $blog_name );
    179         }
    180 
    181         /**
    182          * When the post is published we are faking an activity object
    183          * to which we add 2 properties :
    184          * - the post url
    185          * - the post title
    186          * This is done to build the 'post link' part of the activity
    187          * action string.
    188          * NB: in this case the activity has not yet been created.
    189          */
    190         if ( isset( $activity->post_url ) ) {
    191                 $post_url = $activity->post_url;
    192 
    193         /**
    194          * The post_url property is not set, we need to build the url
    195          * thanks to the post id which is also saved as the secondary
    196          * item id property of the activity object.
    197          */
    198         } else {
    199                 $post_url = add_query_arg( 'p', $activity->secondary_item_id, trailingslashit( $blog_url ) );
    200         }
    201 
    202         // Should be the case when the post has just been published.
    203         if ( isset( $activity->post_title ) ) {
    204                 $post_title = $activity->post_title;
    205 
    206         // If activity already exists try to get the post title from activity meta.
    207         } else if ( ! empty( $activity->id ) ) {
    208                 $post_title = bp_activity_get_meta( $activity->id, 'post_title' );
    209         }
    210 
    211         /**
    212          * In case the post was published without a title
    213          * or the activity meta was not found.
    214          */
    215         if ( empty( $post_title ) ) {
    216                 // Defaults to no title.
    217                 $post_title = __( '(no title)', 'buddypress' );
    218 
    219                 switch_to_blog( $activity->item_id );
    220 
    221                 $post = get_post( $activity->secondary_item_id );
    222                 if ( is_a( $post, 'WP_Post' ) ) {
    223                         // Does the post have a title ?
    224                         if ( ! empty( $post->post_title ) ) {
    225                                 $post_title = $post->post_title;
    226                         }
    227 
    228                         // Make sure the activity exists before saving the post title in activity meta.
    229                         if ( ! empty( $activity->id ) ) {
    230                                 bp_activity_update_meta( $activity->id, 'post_title', $post_title );
    231                         }
    232                 }
    233 
    234                 restore_current_blog();
    235         }
    236 
    237         // Build the 'post link' part of the activity action string.
    238         $post_link = '<a href="' . esc_url( $post_url ) . '">' . esc_html( $post_title ) . '</a>';
    239 
    240217        $user_link = bp_core_get_userlink( $activity->user_id );
    241218
    242219        // Build the complete activity action string.
    243220        if ( is_multisite() ) {
     221                list( $blog_url, $blog_name ) = array_values( bp_blogs_activity_get_site_link_meta( $activity ) );
     222
    244223                $action = sprintf(
    245                         /* translators: 1: the activity user link. 2: the post link. 3: the blog link. */
    246                         esc_html_x( '%1$s wrote a new post, %2$s, on the site %3$s', '`new_blog_post` activity action', 'buddypress' ),
     224                        /* translators: 1: the activity user link. 2: the blog link. */
     225                        esc_html_x( '%1$s wrote a new post on the site %2$s', 'Multisite `new_blog_post` activity action', 'buddypress' ),
    247226                        $user_link,
    248                         $post_link,
    249227                        '<a href="' . esc_url( $blog_url ) . '">' . esc_html( $blog_name ) . '</a>'
    250228                );
    251229        } else {
    252230                $action = sprintf(
    253                         /* translators: 1: the activity user link. 2: the post link. */
    254                         esc_html_x( '%1$s wrote a new post, %2$s', '`new_blog_post` activity action', 'buddypress' ),
    255                         $user_link,
    256                         $post_link
     231                        /* translators: 1: the activity user link. */
     232                        esc_html_x( '%s wrote a new post', '`new_blog_post` activity action', 'buddypress' ),
     233                        $user_link
    257234                );
    258235        }
     
    265242
    266243                if ( ! empty( $post ) && ! is_wp_error( $post ) ) {
    267                         $action = apply_filters( 'bp_blogs_activity_new_post_action', $action, $post, $post_url );
     244                        $post_url = add_query_arg( 'p', $post->ID, trailingslashit( get_home_url( $activity->item_id ) ) );
     245                        $action   = apply_filters_deprecated( 'bp_blogs_activity_new_post_action', array( $action, $post, $post_url ), '2.0.0', 'bp_blogs_format_activity_action_new_blog_post' );
    268246                }
    269247        }
     
    301279         * NB: in this case the activity has not yet been created.
    302280         */
    303 
    304         $blog_url = false;
    305 
    306         // Try to get the blog url from the activity object.
    307         if ( isset( $activity->blog_url ) ) {
    308                 $blog_url = $activity->blog_url;
    309         } else {
    310                 $blog_url = bp_blogs_get_blogmeta( $activity->item_id, 'url' );
    311         }
    312 
    313         $blog_name = false;
    314 
    315         // Try to get the blog name from the activity object.
    316         if ( isset( $activity->blog_name ) ) {
    317                 $blog_name = $activity->blog_name;
    318         } else {
    319                 $blog_name = bp_blogs_get_blogmeta( $activity->item_id, 'name' );
    320         }
    321 
    322         if ( empty( $blog_url ) || empty( $blog_name ) ) {
    323                 $blog_url  = get_home_url( $activity->item_id );
    324                 $blog_name = get_blog_option( $activity->item_id, 'blogname' );
    325 
    326                 bp_blogs_update_blogmeta( $activity->item_id, 'url', $blog_url );
    327                 bp_blogs_update_blogmeta( $activity->item_id, 'name', $blog_name );
    328         }
    329 
    330         $post_url = false;
     281        list( $blog_url, $blog_name ) = array_values( bp_blogs_activity_get_site_link_meta( $activity ) );
     282        $post_url                     = false;
    331283
    332284        // Try to get the post url from the activity object.
     
    402354
    403355                if ( ! empty( $comment ) && ! is_wp_error( $comment ) ) {
    404                         $action = apply_filters( 'bp_blogs_activity_new_comment_action', $action, $comment, $post_url . '#' . $activity->secondary_item_id );
     356                        $action = apply_filters_deprecated( 'bp_blogs_activity_new_comment_action', array( $action, $comment, $post_url . '#' . $activity->secondary_item_id ), '2.0.0', 'bp_blogs_format_activity_action_new_blog_comment' );
    405357                }
    406358        }
  • trunk/src/bp-templates/bp-legacy/css/buddypress-rtl.css

    r13305 r13342  
    333333
    334334#buddypress .activity-list li.new_blog_post .activity-content .activity-inner img {
     335        max-width: 100%;
    335336        float: right;
    336337        margin-left: 0.8em;
  • trunk/src/bp-templates/bp-legacy/css/buddypress.css

    r13305 r13342  
    333333
    334334#buddypress .activity-list li.new_blog_post .activity-content .activity-inner img {
     335        max-width: 100%;
    335336        float: left;
    336337        margin-right: 0.8em;
  • trunk/src/bp-templates/bp-nouveau/common-styles/_bp_activity_entries.scss

    r13305 r13342  
    101101
    102102                                img {
     103                                        max-width: 100%;
    103104                                        float: left;
    104105                                        margin-right: 0.8em;
  • trunk/src/bp-templates/bp-nouveau/css/buddypress-rtl.css

    r13305 r13342  
    13131313
    13141314.activity-list .activity-item.new_blog_post .activity-inner img {
     1315        max-width: 100%;
    13151316        float: right;
    13161317        margin-left: 0.8em;
  • trunk/src/bp-templates/bp-nouveau/css/buddypress.css

    r13305 r13342  
    13131313
    13141314.activity-list .activity-item.new_blog_post .activity-inner img {
     1315        max-width: 100%;
    13151316        float: left;
    13161317        margin-right: 0.8em;
  • trunk/tests/phpunit/testcases/blogs/activity.php

    r12606 r13342  
    7474
    7575                $user_link = bp_core_get_userlink( $u );
    76                 $blog_url = get_home_url();
    77                 $post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
    78                 $post_title = bp_activity_get_meta( $a, 'post_title' );
    79                 $post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
    80 
    81                 $expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
     76                $expected = sprintf( '%s wrote a new post', $user_link );
    8277
    8378                $this->assertSame( $expected, $a_obj->action );
     
    109104                $user_link = bp_core_get_userlink( $u );
    110105                $blog_url = get_home_url();
    111                 $post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
    112                 $post_title = bp_activity_get_meta( $a, 'post_title' );
    113                 $post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
    114 
    115                 $expected = sprintf( '%s wrote a new post, %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . bp_blogs_get_blogmeta( $a_obj->item_id, 'name' ) . '</a>' );
     106
     107                $expected = sprintf( '%s wrote a new post on the site %s', $user_link, '<a href="' . $blog_url . '">' . bp_blogs_get_blogmeta( $a_obj->item_id, 'name' ) . '</a>' );
    116108
    117109                $this->assertSame( $expected, $a_obj->action );
     
    143135                        'item_id' => $b,
    144136                        'secondary_item_id' => $p,
     137                ) );
     138
     139                $a_obj = new BP_Activity_Activity( $a );
     140
     141                $user_link = bp_core_get_userlink( $u );
     142                $blog_url = get_blog_option( $a_obj->item_id, 'home' );
     143
     144                $expected = sprintf( '%s wrote a new post on the site %s', $user_link, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
     145
     146                $this->assertSame( $expected, $a_obj->action );
     147        }
     148
     149        /**
     150         * @group activity_action
     151         * @group bp_blogs_format_activity_action_new_blog_comment
     152         * @group post_type_comment_activities
     153         */
     154        public function test_bp_blogs_format_activity_action_new_blog_comment_ms_nonrootblog() {
     155                if ( ! is_multisite() ) {
     156                        $this->markTestSkipped();
     157                }
     158
     159                $b = self::factory()->blog->create();
     160                $u = self::factory()->user->create();
     161
     162                switch_to_blog( $b );
     163                $p = self::factory()->post->create( array(
     164                        'post_author' => $u,
     165                ) );
     166                $p_obj = get_post( $p );
     167                $c = self::factory()->comment->create( array(
     168                        'comment_post_ID' => $p,
     169                ) );
     170                $c_obj = get_comment( $c );
     171                restore_current_blog();
     172
     173                $a = self::factory()->activity->create( array(
     174                        'component' => buddypress()->blogs->id,
     175                        'type' => 'new_blog_comment',
     176                        'user_id' => $u,
     177                        'item_id' => $b,
     178                        'secondary_item_id' => $c,
    145179                ) );
    146180
     
    153187                $post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
    154188
    155                 $expected = sprintf( '%s wrote a new post, %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
     189                $expected = sprintf( '%s commented on the post, %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
    156190
    157191                $this->assertSame( $expected, $a_obj->action );
    158         }
    159 
    160         /**
    161          * @group activity_action
    162          * @group bp_blogs_format_activity_action_new_blog_comment
    163          * @group post_type_comment_activities
    164          */
    165         public function test_bp_blogs_format_activity_action_new_blog_comment_ms_nonrootblog() {
    166                 if ( ! is_multisite() ) {
    167                         $this->markTestSkipped();
    168                 }
    169 
    170                 $b = self::factory()->blog->create();
    171                 $u = self::factory()->user->create();
    172 
    173                 switch_to_blog( $b );
    174                 $p = self::factory()->post->create( array(
    175                         'post_author' => $u,
    176                 ) );
    177                 $p_obj = get_post( $p );
    178                 $c = self::factory()->comment->create( array(
    179                         'comment_post_ID' => $p,
    180                 ) );
    181                 $c_obj = get_comment( $c );
    182                 restore_current_blog();
    183 
    184                 $a = self::factory()->activity->create( array(
    185                         'component' => buddypress()->blogs->id,
    186                         'type' => 'new_blog_comment',
    187                         'user_id' => $u,
    188                         'item_id' => $b,
    189                         'secondary_item_id' => $c,
    190                 ) );
    191 
    192                 $a_obj = new BP_Activity_Activity( $a );
    193 
    194                 $user_link = bp_core_get_userlink( $u );
    195                 $blog_url = get_blog_option( $a_obj->item_id, 'home' );
    196                 $post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
    197                 $post_title = $p_obj->post_title;
    198                 $post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
    199 
    200                 $expected = sprintf( '%s commented on the post, %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
    201 
    202                 $this->assertSame( $expected, $a_obj->action );
    203         }
    204 
    205         /**
    206          * @group bp_blogs_format_activity_action_new_blog
    207          */
    208         public function test_bp_activity_format_activity_action_new_blog_backpat() {
    209                 if ( ! is_multisite() ) {
    210                         $this->markTestSkipped();
    211                 }
    212 
    213                 add_filter( 'bp_blogs_activity_created_blog_action', array( $this, 'created_blog_passthrough' ), 10, 2 );
    214 
    215                 $b = self::factory()->blog->create();
    216                 $u = self::factory()->user->create();
    217 
    218                 $recorded_blog          = new BP_Blogs_Blog;
    219                 $recorded_blog->user_id = $u;
    220                 $recorded_blog->blog_id = $b;
    221                 $recorded_blog_id       = $recorded_blog->save();
    222 
    223                 $a = self::factory()->activity->create( array(
    224                         'component' => buddypress()->blogs->id,
    225                         'type' => 'new_blog',
    226                         'user_id' => $u,
    227                         'item_id' => $b,
    228                 ) );
    229 
    230                 $this->assertEquals( $this->userblog_id, $recorded_blog_id );
    231         }
    232 
    233         /**
    234          * @group bp_blogs_format_activity_action_new_blog_post
    235          */
    236         public function test_bp_activity_format_activity_action_new_blog_post_backpat() {
    237                 if ( ! is_multisite() ) {
    238                         $this->markTestSkipped();
    239                 }
    240 
    241                 add_filter( 'bp_blogs_activity_new_post_action', array( $this, 'new_post_passthrough' ), 10, 2 );
    242 
    243                 $b = self::factory()->blog->create();
    244 
    245                 switch_to_blog( $b );
    246                 $p = self::factory()->post->create();
    247                 restore_current_blog();
    248 
    249                 $u = self::factory()->user->create();
    250                 $a = self::factory()->activity->create( array(
    251                         'component' => buddypress()->blogs->id,
    252                         'type' => 'new_blog_post',
    253                         'user_id' => $u,
    254                         'item_id' => $b,
    255                         'secondary_item_id' => $p,
    256                 ) );
    257 
    258                 $this->assertEquals( $this->post_id, $p );
    259         }
    260 
    261         /**
    262          * @group bp_blogs_format_activity_action_new_blog_comment
    263          */
    264         public function test_bp_activity_format_activity_action_new_blog_comment_backpat() {
    265                 if ( ! is_multisite() ) {
    266                         $this->markTestSkipped();
    267                 }
    268 
    269                 add_filter( 'bp_blogs_activity_new_comment_action', array( $this, 'new_comment_passthrough' ), 10, 2 );
    270 
    271                 $b = self::factory()->blog->create();
    272 
    273                 switch_to_blog( $b );
    274                 $p = self::factory()->post->create();
    275                 $c = self::factory()->comment->create( array(
    276                         'comment_post_ID' => $p,
    277                 ) );
    278                 restore_current_blog();
    279 
    280                 $u = self::factory()->user->create();
    281                 $a = self::factory()->activity->create( array(
    282                         'component' => buddypress()->blogs->id,
    283                         'type' => 'new_blog_comment',
    284                         'user_id' => $u,
    285                         'item_id' => $b,
    286                         'secondary_item_id' => $c,
    287                 ) );
    288 
    289                 $this->assertEquals( $this->comment_post_id, $p );
    290192        }
    291193
     
    389291                $a_obj = new BP_Activity_Activity( $a );
    390292                $this->assertTrue( ! empty( $a_obj->action ) );
    391 
    392         }
    393 
    394         /**
    395          * @group activity_action
    396          * @group bp_blogs_format_activity_action_new_blog_post
    397          */
    398         public function test_bp_blogs_format_activity_action_new_blog_post_no_title() {
    399                 if ( is_multisite() ) {
    400                         $this->markTestSkipped();
    401                 }
    402 
    403                 buddypress()->activity->actions = new stdClass();
    404 
    405                 $u = self::factory()->user->create();
    406                 $p = wp_insert_post( array(
    407                         'post_author' => $u,
    408                         'post_title'  => '', // no title: the object of the test
    409                         'post_status' => 'publish',
    410                         'post_content' => 'foo bar',
    411                 ) );
    412 
    413                 $user_link = bp_core_get_userlink( $u );
    414                 $blog_url = get_home_url();
    415                 $post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
    416                 $post_link = '<a href="' . $post_url . '">(no title)</a>';
    417 
    418                 // Set activity actions
    419                 bp_activity_get_actions();
    420 
    421                 $a_obj = bp_activity_get( array(
    422                         'item_id'           => 1,
    423                         'secondary_item_id' => $p,
    424                 ) );
    425 
    426                 $expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
    427 
    428                 $this->assertSame( $expected, $a_obj['activities'][0]->action );
    429         }
    430 
    431         /**
    432          * @group activity_action
    433          * @group bp_blogs_format_activity_action_new_blog_post
    434          */
    435         public function test_bp_blogs_format_activity_action_new_blog_post_updated_without_title() {
    436                 if ( is_multisite() ) {
    437                         $this->markTestSkipped();
    438                 }
    439 
    440                 buddypress()->activity->actions = new stdClass();
    441 
    442                 $u = self::factory()->user->create();
    443                 $p = wp_insert_post( array(
    444                         'post_author' => $u,
    445                         'post_title'  => 'foo',
    446                         'post_status' => 'publish',
    447                         'post_content' => 'foo bar',
    448                 ) );
    449 
    450                 $user_link  = bp_core_get_userlink( $u );
    451                 $blog_url   = get_home_url();
    452                 $post_url   = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
    453                 $post_title = get_the_title( $p );
    454                 $post_link  = '<a href="' . $post_url . '">' . $post_title . '</a>';
    455 
    456                 // Set actions
    457                 bp_activity_get_actions();
    458 
    459                 $a_obj = bp_activity_get( array(
    460                         'item_id'           => 1,
    461                         'secondary_item_id' => $p,
    462                 ) );
    463 
    464                 $expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
    465 
    466                 $this->assertSame( $expected, $a_obj['activities'][0]->action );
    467 
    468                 // Update the post by removing its title
    469                 wp_update_post( array(
    470                         'ID'         => $p,
    471                         'post_title' => '',
    472                 ) );
    473 
    474                 // we now expect the (no title) post link
    475                 $post_link = '<a href="' . $post_url . '">(no title)</a>';
    476                 $expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
    477 
    478                 $a_obj = bp_activity_get( array(
    479                         'item_id'           => 1,
    480                         'secondary_item_id' => $p,
    481                 ) );
    482 
    483                 $this->assertSame( $expected, $a_obj['activities'][0]->action );
    484293        }
    485294
Note: See TracChangeset for help on using the changeset viewer.