Changeset 43084
- Timestamp:
- 05/02/2018 01:03:53 AM (6 years ago)
- Location:
- branches/4.9
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/4.9
- Property svn:mergeinfo changed
/trunk merged: 43011,43014
- Property svn:mergeinfo changed
-
branches/4.9/src/wp-admin/includes/ajax-actions.php
r43083 r43084 4155 4155 4156 4156 // Find the request CPT 4157 $request = get_post( $request_id ); 4158 if ( 'remove_personal_data' !== $request->post_title ) { 4157 $request = wp_get_user_request_data( $request_id ); 4158 4159 if ( ! $request || 'remove_personal_data' !== $request->action_name ) { 4159 4160 wp_send_json_error( __( 'Error: Invalid request ID.' ) ); 4160 4161 } 4161 4162 4162 $email_address = get_post_meta( $request_id, '_wp_user_request_user_email', true );4163 $email_address = $request->email; 4163 4164 4164 4165 if ( ! is_email( $email_address ) ) { -
branches/4.9/src/wp-admin/includes/user.php
r43083 r43084 586 586 587 587 update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() ); 588 588 589 $request = wp_update_post( array( 589 'ID' => $request_ data['request_id'],590 'ID' => $request_id, 590 591 'post_status' => 'request-confirmed', 591 592 ) ); 593 592 594 return $request; 593 595 } … … 732 734 733 735 /** 736 * Cleans up failed and expired requests before displaying the list table. 737 * 738 * @since 4.9.6 739 * @access private 740 */ 741 function _wp_personal_data_cleanup_requests() { 742 $expires = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); 743 $requests_query = new WP_Query( array( 744 'post_type' => 'user_request', 745 'posts_per_page' => -1, 746 'post_status' => 'request-pending', 747 'fields' => 'ids', 748 'date_query' => array( 749 array( 750 'column' => 'post_modified_gmt', 751 'before' => $expires . ' seconds ago', 752 ), 753 ), 754 ) ); 755 756 $request_ids = $requests_query->posts; 757 758 foreach ( $request_ids as $request_id ) { 759 wp_update_post( array( 760 'ID' => $request_id, 761 'post_status' => 'request-failed', 762 'post_password' => '', 763 ) ); 764 } 765 } 766 767 /** 734 768 * Personal data export. 735 769 * … … 743 777 744 778 _wp_personal_data_handle_actions(); 779 _wp_personal_data_cleanup_requests(); 745 780 746 781 $requests_table = new WP_Privacy_Data_Export_Requests_Table( array( … … 804 839 805 840 _wp_personal_data_handle_actions(); 841 _wp_personal_data_cleanup_requests(); 806 842 807 843 // "Borrow" xfn.js for now so we don't have to create new files. … … 842 878 <form class="search-form wp-clearfix"> 843 879 <?php $requests_table->search_box( __( 'Search Requests' ), 'requests' ); ?> 844 <input type="hidden" name="page" value=" export_personal_data" />880 <input type="hidden" name="page" value="remove_personal_data" /> 845 881 <input type="hidden" name="filter-status" value="<?php echo isset( $_REQUEST['filter-status'] ) ? esc_attr( sanitize_text_field( $_REQUEST['filter-status'] ) ) : ''; ?>" /> 846 882 <input type="hidden" name="orderby" value="<?php echo isset( $_REQUEST['orderby'] ) ? esc_attr( sanitize_text_field( $_REQUEST['orderby'] ) ) : ''; ?>" /> … … 908 944 public function get_columns() { 909 945 $columns = array( 910 'cb' 911 'email' 912 'status' 913 ' requested_timestamp' => __( 'Requested' ),914 'next_steps' 946 'cb' => '<input type="checkbox" />', 947 'email' => __( 'Requester' ), 948 'status' => __( 'Status' ), 949 'created_timestamp' => __( 'Requested' ), 950 'next_steps' => __( 'Next Steps' ), 915 951 ); 916 952 return $columns; … … 960 996 FROM {$wpdb->posts} 961 997 WHERE post_type = %s 962 AND post_ title = %s998 AND post_name = %s 963 999 GROUP BY post_status"; 964 1000 … … 1048 1084 foreach ( $request_ids as $request_id ) { 1049 1085 $resend = _wp_privacy_resend_request( $request_id ); 1050 1086 1051 1087 if ( $resend && ! is_wp_error( $resend ) ) { 1052 1088 $count++; … … 1084 1120 $args = array( 1085 1121 'post_type' => $this->post_type, 1086 ' title' => $this->request_type,1122 'post_name__in' => array( $this->request_type ), 1087 1123 'posts_per_page' => $posts_per_page, 1088 1124 'offset' => isset( $_REQUEST['paged'] ) ? max( 0, absint( $_REQUEST['paged'] ) - 1 ) * $posts_per_page: 0, 1089 1125 'post_status' => 'any', 1126 's' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '', 1090 1127 ); 1091 1128 … … 1095 1132 } 1096 1133 1097 if ( ! empty( $_REQUEST['s'] ) ) {1098 $args['meta_query'] = array(1099 $name_query,1100 'relation' => 'AND',1101 array(1102 'key' => '_wp_user_request_user_email',1103 'value' => isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ): '',1104 'compare' => 'LIKE',1105 ),1106 );1107 }1108 1109 1134 $requests_query = new WP_Query( $args ); 1110 1135 $requests = $requests_query->posts; … … 1113 1138 $this->items[] = wp_get_user_request_data( $request->ID ); 1114 1139 } 1140 1141 $this->items = array_filter( $this->items ); 1115 1142 1116 1143 $this->set_pagination_args( … … 1127 1154 * @since 4.9.6 1128 1155 * 1129 * @param array$item Item being shown.1156 * @param WP_User_Request $item Item being shown. 1130 1157 * @return string 1131 1158 */ 1132 1159 public function column_cb( $item ) { 1133 return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item ['request_id']) );1160 return sprintf( '<input type="checkbox" name="request_id[]" value="%1$s" /><span class="spinner"></span>', esc_attr( $item->ID ) ); 1134 1161 } 1135 1162 … … 1139 1166 * @since 4.9.6 1140 1167 * 1141 * @param array$item Item being shown.1168 * @param WP_User_Request $item Item being shown. 1142 1169 * @return string 1143 1170 */ 1144 1171 public function column_status( $item ) { 1145 $status = get_post_status( $item ['request_id']);1172 $status = get_post_status( $item->ID ); 1146 1173 $status_object = get_post_status_object( $status ); 1147 1174 … … 1154 1181 switch ( $status ) { 1155 1182 case 'request-confirmed': 1156 $timestamp = $item ['confirmed_timestamp'];1183 $timestamp = $item->confirmed_timestamp; 1157 1184 break; 1158 1185 case 'request-completed': 1159 $timestamp = $item ['completed_timestamp'];1186 $timestamp = $item->completed_timestamp; 1160 1187 break; 1161 1188 } … … 1198 1225 * @since 4.9.6 1199 1226 * 1200 * @param array$item Item being shown.1201 * @param string $column_name Name of column being shown.1227 * @param WP_User_Request $item Item being shown. 1228 * @param string $column_name Name of column being shown. 1202 1229 * @return string 1203 1230 */ 1204 1231 public function column_default( $item, $column_name ) { 1205 $cell_value = $item [ $column_name ];1206 1207 if ( in_array( $column_name, array( ' requested_timestamp' ), true ) ) {1232 $cell_value = $item->$column_name; 1233 1234 if ( in_array( $column_name, array( 'created_timestamp' ), true ) ) { 1208 1235 return $this->get_timestamp_as_date( $cell_value ); 1209 1236 } … … 1217 1244 * @since 4.9.6 1218 1245 * 1219 * @param array$item Item being shown.1246 * @param WP_User_Request $item Item being shown. 1220 1247 * @return string 1221 1248 */ 1222 1249 public function column_email( $item ) { 1223 return sprintf( '%1$s %2$s', $item ['email'], $this->row_actions( array() ) );1250 return sprintf( '%1$s %2$s', $item->email, $this->row_actions( array() ) ); 1224 1251 } 1225 1252 … … 1229 1256 * @since 4.9.6 1230 1257 * 1231 * @param array$item Item being shown.1258 * @param WP_User_Request $item Item being shown. 1232 1259 */ 1233 1260 public function column_next_steps( $item ) {} … … 1238 1265 * @since 4.9.6 1239 1266 * 1240 * @param object $item The current item1267 * @param WP_User_Request $item The current item 1241 1268 */ 1242 1269 public function single_row( $item ) { 1243 $status = get_post_status( $item['request_id'] );1270 $status = $item->status; 1244 1271 1245 1272 echo '<tr class="status-' . esc_attr( $status ) . '">'; … … 1285 1312 * @since 4.9.6 1286 1313 * 1287 * @param array$item Item being shown.1314 * @param WP_User_Request $item Item being shown. 1288 1315 * @return string 1289 1316 */ … … 1291 1318 $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() ); 1292 1319 $exporters_count = count( $exporters ); 1293 $request_id = $item ['request_id'];1320 $request_id = $item->ID; 1294 1321 $nonce = wp_create_nonce( 'wp-privacy-export-personal-data-' . $request_id ); 1295 1322 … … 1308 1335 ); 1309 1336 1310 return sprintf( '%1$s %2$s', $item ['email'], $this->row_actions( $row_actions ) );1337 return sprintf( '%1$s %2$s', $item->email, $this->row_actions( $row_actions ) ); 1311 1338 } 1312 1339 … … 1316 1343 * @since 4.9.6 1317 1344 * 1318 * @param array$item Item being shown.1345 * @param WP_User_Request $item Item being shown. 1319 1346 */ 1320 1347 public function column_next_steps( $item ) { 1321 $status = get_post_status( $item['request_id'] );1348 $status = $item->status; 1322 1349 1323 1350 switch ( $status ) { … … 1329 1356 break; 1330 1357 case 'request-failed': 1331 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item ['request_id']. ']', false );1358 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); 1332 1359 break; 1333 1360 case 'request-completed': 1334 1361 echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array( 1335 1362 'action' => 'delete', 1336 'request_id' => array( $item ['request_id'])1363 'request_id' => array( $item->ID ) 1337 1364 ), admin_url( 'tools.php?page=export_personal_data' ) ), 'bulk-privacy_requests' ) ) . '">' . esc_html__( 'Remove request' ) . '</a>'; 1338 1365 break; … … 1370 1397 * @since 4.9.6 1371 1398 * 1372 * @param array$item Item being shown.1399 * @param WP_User_Request $item Item being shown. 1373 1400 * @return string 1374 1401 */ … … 1376 1403 $row_actions = array(); 1377 1404 1378 // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received 1379 $status = get_post_status( $item['request_id'] );1405 // Allow the administrator to "force remove" the personal data even if confirmation has not yet been received. 1406 $status = $item->status; 1380 1407 if ( 'request-confirmed' !== $status ) { 1381 1408 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 1382 1409 $erasers_count = count( $erasers ); 1383 $request_id = $item ['request_id'];1410 $request_id = $item->ID; 1384 1411 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); 1385 1412 … … 1399 1426 } 1400 1427 1401 return sprintf( '%1$s %2$s', $item ['email'], $this->row_actions( $row_actions ) );1428 return sprintf( '%1$s %2$s', $item->email, $this->row_actions( $row_actions ) ); 1402 1429 } 1403 1430 … … 1407 1434 * @since 4.9.6 1408 1435 * 1409 * @param array$item Item being shown.1436 * @param WP_User_Request $item Item being shown. 1410 1437 */ 1411 1438 public function column_next_steps( $item ) { 1412 $status = get_post_status( $item['request_id'] );1439 $status = $item->status; 1413 1440 1414 1441 switch ( $status ) { … … 1419 1446 $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() ); 1420 1447 $erasers_count = count( $erasers ); 1421 $request_id = $item ['request_id'];1448 $request_id = $item->ID; 1422 1449 $nonce = wp_create_nonce( 'wp-privacy-erase-personal-data-' . $request_id ); 1423 1450 … … 1437 1464 break; 1438 1465 case 'request-failed': 1439 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item ['request_id']. ']', false );1466 submit_button( __( 'Retry' ), 'secondary', 'privacy_action_email_retry[' . $item->ID . ']', false ); 1440 1467 break; 1441 1468 case 'request-completed': 1442 1469 echo '<a href="' . esc_url( wp_nonce_url( add_query_arg( array( 1443 1470 'action' => 'delete', 1444 'request_id' => array( $item ['request_id']),1471 'request_id' => array( $item->ID ), 1445 1472 ), admin_url( 'tools.php?page=remove_personal_data' ) ), 'bulk-privacy_requests' ) ) . '">' . esc_html__( 'Remove request' ) . '</a>'; 1446 1473 break; -
branches/4.9/src/wp-includes/post.php
r43083 r43084 3801 3801 */ 3802 3802 function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) { 3803 if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) )3803 if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) || 'user_request' === $post_type ) 3804 3804 return $slug; 3805 3805 -
branches/4.9/src/wp-includes/user.php
r43083 r43084 2763 2763 } 2764 2764 2765 if ( ! in_array( $request_data ['status'], array( 'request-pending', 'request-failed' ), true ) ) {2765 if ( ! in_array( $request_data->status, array( 'request-pending', 'request-failed' ), true ) ) { 2766 2766 return; 2767 2767 } … … 2769 2769 update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() ); 2770 2770 wp_update_post( array( 2771 'ID' => $request_ data['request_id'],2771 'ID' => $request_id, 2772 2772 'post_status' => 'request-confirmed', 2773 2773 ) ); … … 2785 2785 $request = wp_get_user_request_data( $request_id ); 2786 2786 2787 if ( $request && in_array( $request ['action'], _wp_privacy_action_request_types(), true ) ) {2787 if ( $request && in_array( $request->action_name, _wp_privacy_action_request_types(), true ) ) { 2788 2788 $message = '<p class="message">' . __( 'Action has been confirmed.' ) . '</p>'; 2789 2789 $message .= __( 'The site administrator has been notified and will fulfill your request as soon as possible.' ); … … 2823 2823 // Check for duplicates. 2824 2824 $requests_query = new WP_Query( array( 2825 'post_type' => 'user_request', 2826 'title' => $action_name, 2827 'post_status' => 'any', 2828 'fields' => 'ids', 2829 'meta_query' => array( 2830 array( 2831 'key' => '_wp_user_request_user_email', 2832 'value' => $email_address, 2833 ), 2834 ), 2825 'post_type' => 'user_request', 2826 'post_name__in' => array( $action_name ), // Action name stored in post_name column. 2827 'title' => $email_address, // Email address stored in post_title column. 2828 'post_status' => 'any', 2829 'fields' => 'ids', 2835 2830 ) ); 2836 2831 … … 2841 2836 $request_id = wp_insert_post( array( 2842 2837 'post_author' => $user_id, 2843 'post_title' => $action_name, 2838 'post_name' => $action_name, 2839 'post_title' => $email_address, 2844 2840 'post_content' => wp_json_encode( $request_data ), 2845 2841 'post_status' => 'request-pending', … … 2848 2844 'post_date_gmt' => current_time( 'mysql', true ), 2849 2845 ), true ); 2850 2851 if ( is_wp_error( $request_id ) ) {2852 return $request_id;2853 }2854 2855 update_post_meta( $request_id, '_wp_user_request_user_email', $email_address );2856 update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', false );2857 2846 2858 2847 return $request_id; … … 2884 2873 * Filters the user action description. 2885 2874 * 2875 * @since 4.9.6 2876 * 2886 2877 * @param string $description The default description. 2887 2878 * @param string $action_name The name of the request. 2888 */ 2879 */ 2889 2880 return apply_filters( 'user_request_action_description', $description, $action_name ); 2890 2881 } … … 2902 2893 function wp_send_user_request( $request_id ) { 2903 2894 $request_id = absint( $request_id ); 2904 $request = get_post( $request_id );2905 2906 if ( ! $request || 'user_request' !== $request->post_type) {2895 $request = wp_get_user_request_data( $request_id ); 2896 2897 if ( ! $request ) { 2907 2898 return new WP_Error( 'user_request_error', __( 'Invalid request.' ) ); 2908 2899 } 2909 2900 2910 if ( 'request-pending' !== $request->post_status ) {2911 wp_update_post( array(2912 'ID' => $request_id,2913 'post_status' => 'request-pending',2914 'post_date' => current_time( 'mysql', false ),2915 'post_date_gmt' => current_time( 'mysql', true ),2916 ) );2917 }2918 2919 2901 $email_data = array( 2920 'action_name' => $request->post_title, 2921 'email' => get_post_meta( $request->ID, '_wp_user_request_user_email', true ), 2922 'description' => wp_user_request_action_description( $request->post_title ), 2902 'email' => $request->email, 2903 'description' => wp_user_request_action_description( $request->action_name ), 2923 2904 'confirm_url' => add_query_arg( array( 2924 2905 'action' => 'confirmaction', … … 2968 2949 * Data relating to the account action email. 2969 2950 * 2970 * @type string $action_name Name of the action being performed.2971 * @type string $email The email address this is being sent to.2972 * @type string $description Description of the action being performed so the user knows what the email is for.2973 * @type string $confirm_url The link to click on to confirm the account action.2974 * @type string $sitename The site name sending the mail.2975 * @type string $siteurl The site URL sending the mail.2951 * @type WP_User_Request $request User request object. 2952 * @type string $email The email address this is being sent to. 2953 * @type string $description Description of the action being performed so the user knows what the email is for. 2954 * @type string $confirm_url The link to click on to confirm the account action. 2955 * @type string $sitename The site name sending the mail. 2956 * @type string $siteurl The site URL sending the mail. 2976 2957 * } 2977 2958 */ … … 2989 2970 2990 2971 /** 2991 * Returns a confirmation key for a user action and stores the hashed version .2972 * Returns a confirmation key for a user action and stores the hashed version for future comparison. 2992 2973 * 2993 2974 * @since 4.9.6 … … 3008 2989 } 3009 2990 3010 update_post_meta( $request_id, '_wp_user_request_confirm_key', $wp_hasher->HashPassword( $key ) ); 3011 update_post_meta( $request_id, '_wp_user_request_confirm_key_timestamp', time() ); 2991 wp_update_post( array( 2992 'ID' => $request_id, 2993 'post_status' => 'request-pending', 2994 'post_password' => $wp_hasher->HashPassword( $key ), 2995 'post_modified' => current_time( 'mysql', false ), 2996 'post_modified_gmt' => current_time( 'mysql', true ), 2997 ) ); 3012 2998 3013 2999 return $key; … … 3033 3019 } 3034 3020 3035 if ( ! in_array( $request ['status'], array( 'request-pending', 'request-failed' ), true ) ) {3021 if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { 3036 3022 return __( 'This link has expired.' ); 3037 3023 } … … 3046 3032 } 3047 3033 3048 $key_request_time = $request ['confirm_key_timestamp'];3049 $saved_key = $request ['confirm_key'];3034 $key_request_time = $request->modified_timestamp; 3035 $saved_key = $request->confirm_key; 3050 3036 3051 3037 if ( ! $saved_key ) { … … 3088 3074 function wp_get_user_request_data( $request_id ) { 3089 3075 $request_id = absint( $request_id ); 3090 $ request= get_post( $request_id );3091 3092 if ( ! $ request || 'user_request' !== $request->post_type ) {3076 $post = get_post( $request_id ); 3077 3078 if ( ! $post || 'user_request' !== $post->post_type ) { 3093 3079 return false; 3094 3080 } 3095 3081 3096 return array( 3097 'request_id' => $request->ID, 3098 'user_id' => $request->post_author, 3099 'email' => get_post_meta( $request->ID, '_wp_user_request_user_email', true ), 3100 'action' => $request->post_title, 3101 'requested_timestamp' => strtotime( $request->post_date_gmt ), 3102 'confirmed_timestamp' => get_post_meta( $request->ID, '_wp_user_request_confirmed_timestamp', true ), 3103 'completed_timestamp' => get_post_meta( $request->ID, '_wp_user_request_completed_timestamp', true ), 3104 'request_data' => json_decode( $request->post_content, true ), 3105 'status' => $request->post_status, 3106 'confirm_key' => get_post_meta( $request_id, '_wp_user_request_confirm_key', true ), 3107 'confirm_key_timestamp' => get_post_meta( $request_id, '_wp_user_request_confirm_key_timestamp', true ), 3108 ); 3109 } 3082 return new WP_User_Request( $post ); 3083 } 3084 3085 /** 3086 * WP_User_Request class. 3087 * 3088 * Represents user request data loaded from a WP_Post object. 3089 * 3090 * @since 4.9.6 3091 */ 3092 final class WP_User_Request { 3093 /** 3094 * Request ID. 3095 * 3096 * @var int 3097 */ 3098 public $ID = 0; 3099 3100 /** 3101 * User ID. 3102 * 3103 * @var int 3104 */ 3105 3106 public $user_id = 0; 3107 3108 /** 3109 * User email. 3110 * 3111 * @var int 3112 */ 3113 public $email = ''; 3114 3115 /** 3116 * Action name. 3117 * 3118 * @var string 3119 */ 3120 public $action_name = ''; 3121 3122 /** 3123 * Current status. 3124 * 3125 * @var string 3126 */ 3127 public $status = ''; 3128 3129 /** 3130 * Timestamp this request was created. 3131 * 3132 * @var int|null 3133 */ 3134 public $created_timestamp = null; 3135 3136 /** 3137 * Timestamp this request was last modified. 3138 * 3139 * @var int|null 3140 */ 3141 public $modified_timestamp = null; 3142 3143 /** 3144 * Timestamp this request was confirmed. 3145 * 3146 * @var int 3147 */ 3148 public $confirmed_timestamp = null; 3149 3150 /** 3151 * Timestamp this request was completed. 3152 * 3153 * @var int 3154 */ 3155 public $completed_timestamp = null; 3156 3157 /** 3158 * Misc data assigned to this request. 3159 * 3160 * @var array 3161 */ 3162 public $request_data = array(); 3163 3164 /** 3165 * Key used to confirm this request. 3166 * 3167 * @var string 3168 */ 3169 public $confirm_key = ''; 3170 3171 /** 3172 * Constructor. 3173 * 3174 * @since 4.9.6 3175 * 3176 * @param WP_Post|object $post Post object. 3177 */ 3178 public function __construct( $post ) { 3179 $this->ID = $post->ID; 3180 $this->user_id = $post->post_author; 3181 $this->email = $post->post_title; 3182 $this->action_name = $post->post_name; 3183 $this->status = $post->post_status; 3184 $this->created_timestamp = strtotime( $post->post_date_gmt ); 3185 $this->modified_timestamp = strtotime( $post->post_modified_gmt ); 3186 $this->confirmed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_confirmed_timestamp', true ); 3187 $this->completed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_completed_timestamp', true ); 3188 $this->request_data = json_decode( $post->post_content, true ); 3189 $this->confirm_key = $post->post_password; 3190 } 3191 }
Note: See TracChangeset
for help on using the changeset viewer.