| 1 | Index: wp-includes/default-filters.php |
|---|
| 2 | =================================================================== |
|---|
| 3 | --- wp-includes/default-filters.php (revision 7746) |
|---|
| 4 | +++ wp-includes/default-filters.php (working copy) |
|---|
| 5 | @@ -178,6 +178,7 @@ |
|---|
| 6 | add_action('init', 'smilies_init', 5); |
|---|
| 7 | add_action( 'plugins_loaded', 'wp_maybe_load_widgets', 0 ); |
|---|
| 8 | add_action( 'shutdown', 'wp_ob_end_flush_all', 1); |
|---|
| 9 | +add_action( 'pre_post_update', 'wp_save_revision' ); |
|---|
| 10 | add_action('publish_post', '_publish_post_hook', 5, 1); |
|---|
| 11 | add_action('future_post', '_future_post_hook', 5, 2); |
|---|
| 12 | add_action('future_page', '_future_post_hook', 5, 2); |
|---|
| 13 | Index: wp-includes/post-template.php |
|---|
| 14 | =================================================================== |
|---|
| 15 | --- wp-includes/post-template.php (revision 7746) |
|---|
| 16 | +++ wp-includes/post-template.php (working copy) |
|---|
| 17 | @@ -564,4 +564,136 @@ |
|---|
| 18 | return false; |
|---|
| 19 | } |
|---|
| 20 | |
|---|
| 21 | -?> |
|---|
| 22 | +/** |
|---|
| 23 | + * wp_post_revision_time() - returns formatted datetimestamp of a revision |
|---|
| 24 | + * |
|---|
| 25 | + * @package WordPress |
|---|
| 26 | + * @subpackage Post Revisions |
|---|
| 27 | + * @since 2.6 |
|---|
| 28 | + * |
|---|
| 29 | + * @uses wp_get_revision() |
|---|
| 30 | + * @uses date_i18n() |
|---|
| 31 | + * |
|---|
| 32 | + * @param int|object $revision revision ID or revision object |
|---|
| 33 | + * @return string i18n formatted datetimestamp or localized 'Corrent Revision' |
|---|
| 34 | + */ |
|---|
| 35 | +function wp_post_revision_time( $revision ) { |
|---|
| 36 | + if ( !$revision = wp_get_revision( $revision ) ) { |
|---|
| 37 | + if ( $revision = get_post( $revision ) ) |
|---|
| 38 | + return __( 'Current Revision' ); |
|---|
| 39 | + return $revision; |
|---|
| 40 | + } |
|---|
| 41 | + |
|---|
| 42 | + $datef = _c( 'j F, Y @ G:i|revision date format'); |
|---|
| 43 | + return date_i18n( $datef, strtotime( $revision->post_date_gmt . ' +0000' ) ); |
|---|
| 44 | +} |
|---|
| 45 | + |
|---|
| 46 | +/** |
|---|
| 47 | + * wp_list_post_revisions() - echoes list of a post's revisions |
|---|
| 48 | + * |
|---|
| 49 | + * Can output either a UL with edit links or a TABLE with diff interface, and restore action links |
|---|
| 50 | + * |
|---|
| 51 | + * Second argument controls parameters: |
|---|
| 52 | + * (bool) parent : include the parent (the "Current Revision") in the list |
|---|
| 53 | + * (string) format : 'list' or 'form-table'. 'list' outputs UL, 'form-table' outputs TABLE with UI |
|---|
| 54 | + * (int) right : what revision is currently being viewed - used in form-table format |
|---|
| 55 | + * (int) left : what revision is currently being diffed against right - used in form-table format |
|---|
| 56 | + * |
|---|
| 57 | + * @package WordPress |
|---|
| 58 | + * @subpackage Post Revisions |
|---|
| 59 | + * @since 2.6 |
|---|
| 60 | + * |
|---|
| 61 | + * @uses wp_get_post_revisions() |
|---|
| 62 | + * @uses wp_post_revision_time() |
|---|
| 63 | + * @uses get_edit_post_link() |
|---|
| 64 | + * @uses get_author_name() |
|---|
| 65 | + * |
|---|
| 66 | + * @param int|object $post_id post ID or post object |
|---|
| 67 | + * @param string|array $args see description @see wp_parse_args() |
|---|
| 68 | + */ |
|---|
| 69 | +function wp_list_post_revisions( $post_id = 0, $args = null ) { // TODO? split into two functions (list, form-table) ? |
|---|
| 70 | + if ( !$post = get_post( $post_id ) ) |
|---|
| 71 | + return; |
|---|
| 72 | + |
|---|
| 73 | + if ( !$revisions = wp_get_post_revisions( $post->ID ) ) |
|---|
| 74 | + return; |
|---|
| 75 | + |
|---|
| 76 | + $defaults = array( 'parent' => false, 'right' => false, 'left' => false, 'format' => 'list' ); |
|---|
| 77 | + extract( wp_parse_args( $args, $defaults ), EXTR_SKIP ); |
|---|
| 78 | + |
|---|
| 79 | + $titlef = _c( '%1$s by %2$s|post revision 1:datetime, 2:name' ); |
|---|
| 80 | + |
|---|
| 81 | + if ( $parent ) |
|---|
| 82 | + array_unshift( $revisions, $post ); |
|---|
| 83 | + |
|---|
| 84 | + $rows = ''; |
|---|
| 85 | + $class = false; |
|---|
| 86 | + foreach ( $revisions as $revision ) { |
|---|
| 87 | + $date = wp_post_revision_time( $revision ); |
|---|
| 88 | + if ( $link = get_edit_post_link( $revision->ID ) ) |
|---|
| 89 | + $date = "<a href='$link'>$date</a>"; |
|---|
| 90 | + $name = get_author_name( $revision->post_author ); |
|---|
| 91 | + |
|---|
| 92 | + if ( 'form-table' == $format ) { |
|---|
| 93 | + if ( $left ) |
|---|
| 94 | + $old_checked = $left == $revision->ID ? ' checked="checked"' : ''; |
|---|
| 95 | + else |
|---|
| 96 | + $old_checked = $new_checked ? ' checked="checked"' : ''; |
|---|
| 97 | + $new_checked = $right == $revision->ID ? ' checked="checked"' : ''; |
|---|
| 98 | + |
|---|
| 99 | + $class = $class ? '' : " class='alternate'"; |
|---|
| 100 | + |
|---|
| 101 | + if ( $post->ID != $revision->ID && current_user_can( 'edit_post', $post->ID ) ) |
|---|
| 102 | + $actions = '<a href="' . wp_nonce_url( add_query_arg( array( 'revision' => $revision->ID, 'diff' => false, 'restore' => 'restore' ) ), "restore-post_$post->ID|$revision->ID" ) . '">' . __( 'Restore' ) . '</a>'; |
|---|
| 103 | + else |
|---|
| 104 | + $actions = ''; |
|---|
| 105 | + |
|---|
| 106 | + $rows .= "<tr$class>\n"; |
|---|
| 107 | + $rows .= "\t<th style='white-space: nowrap' scope='row'><input type='radio' name='diff' value='$revision->ID'$old_checked /><input type='radio' name='revision' value='$revision->ID'$new_checked />\n"; |
|---|
| 108 | + $rows .= "\t<td>$date</td>\n"; |
|---|
| 109 | + $rows .= "\t<td>$name</td>\n"; |
|---|
| 110 | + $rows .= "\t<td class='action-links'>$actions</td>\n"; |
|---|
| 111 | + $rows .= "</tr>\n"; |
|---|
| 112 | + } else { |
|---|
| 113 | + $rows .= "\t<li>" . sprintf( $titlef, $date, $name ). "</li>\n"; |
|---|
| 114 | + } |
|---|
| 115 | + } |
|---|
| 116 | + |
|---|
| 117 | + if ( 'form-table' == $format ) : ?> |
|---|
| 118 | + |
|---|
| 119 | +<form action="revision.php" method="get"> |
|---|
| 120 | + |
|---|
| 121 | +<div class="tablenav"> |
|---|
| 122 | + <div class="alignleft"> |
|---|
| 123 | + <input type="submit" class="button-secondary" value="<?php _e( 'Compare Revisions' ); ?>" /> |
|---|
| 124 | + </div> |
|---|
| 125 | +</div> |
|---|
| 126 | + |
|---|
| 127 | +<br class="clear" /> |
|---|
| 128 | + |
|---|
| 129 | +<table class="widefat post-revisions"> |
|---|
| 130 | + <col /> |
|---|
| 131 | + <col style="width: 33%" /> |
|---|
| 132 | + <col style="width: 33%" /> |
|---|
| 133 | + <col style="width: 33%" /> |
|---|
| 134 | +<thead> |
|---|
| 135 | + <th scope="col"></th> |
|---|
| 136 | + <th scope="col"><?php _e( 'Date Created' ); ?></th> |
|---|
| 137 | + <th scope="col"><?php _e( 'Author' ); ?></th> |
|---|
| 138 | + <th scope="col" class="action-links"><?php _e( 'Actions' ); ?></th> |
|---|
| 139 | +</thead> |
|---|
| 140 | +<tbody> |
|---|
| 141 | + |
|---|
| 142 | +<?php echo $rows; ?> |
|---|
| 143 | + |
|---|
| 144 | +</tbody> |
|---|
| 145 | +</table> |
|---|
| 146 | + |
|---|
| 147 | +<?php |
|---|
| 148 | + else : |
|---|
| 149 | + echo "<ul class='post-revisions'>\n"; |
|---|
| 150 | + echo $rows; |
|---|
| 151 | + echo "</ul>"; |
|---|
| 152 | + endif; |
|---|
| 153 | + |
|---|
| 154 | +} |
|---|
| 155 | Index: wp-includes/post.php |
|---|
| 156 | =================================================================== |
|---|
| 157 | --- wp-includes/post.php (revision 7746) |
|---|
| 158 | +++ wp-includes/post.php (working copy) |
|---|
| 159 | @@ -2990,4 +2990,232 @@ |
|---|
| 160 | } |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | +/* Post Revisions */ |
|---|
| 164 | + |
|---|
| 165 | +/** |
|---|
| 166 | + * _wp_revision_fields() - determines which fields of posts are to be saved in revisions |
|---|
| 167 | + * |
|---|
| 168 | + * Does two things. If passed a postn *array*, it will return a post array ready to be |
|---|
| 169 | + * insterted into the posts table as a post revision. |
|---|
| 170 | + * Otherwise, returns an array whose keys are the post fields to be saved post revisions. |
|---|
| 171 | + * |
|---|
| 172 | + * @package WordPress |
|---|
| 173 | + * @subpackage Post Revisions |
|---|
| 174 | + * @since 2.6 |
|---|
| 175 | + * |
|---|
| 176 | + * @param array $post optional a post array to be processed for insertion as a post revision |
|---|
| 177 | + * @return array post array ready to be inserted as a post revision or array of fields that can be versioned |
|---|
| 178 | + */ |
|---|
| 179 | +function _wp_revision_fields( $post = null ) { |
|---|
| 180 | + static $fields = false; |
|---|
| 181 | + |
|---|
| 182 | + if ( !$fields ) { |
|---|
| 183 | + // Allow these to be versioned |
|---|
| 184 | + $fields = array( |
|---|
| 185 | + 'post_title' => __( 'Title' ), |
|---|
| 186 | + 'post_author' => __( 'Author' ), |
|---|
| 187 | + 'post_content' => __( 'Content' ), |
|---|
| 188 | + 'post_excerpt' => __( 'Excerpt' ), |
|---|
| 189 | + ); |
|---|
| 190 | + |
|---|
| 191 | + // WP uses these internally either in versioning or elsewhere - they cannot be versioned |
|---|
| 192 | + foreach ( array( 'ID', 'post_name', 'post_parent', 'post_date', 'post_date_gmt', 'post_status', 'post_type', 'comment_count' ) as $protect ) |
|---|
| 193 | + unset( $fields[$protect] ); |
|---|
| 194 | + } |
|---|
| 195 | + |
|---|
| 196 | + if ( !is_array($post) ) |
|---|
| 197 | + return $fields; |
|---|
| 198 | + |
|---|
| 199 | + $return = array(); |
|---|
| 200 | + foreach ( array_intersect( array_keys( $post ), array_keys( $fields ) ) as $field ) |
|---|
| 201 | + $return[$field] = $post[$field]; |
|---|
| 202 | + |
|---|
| 203 | + $return['post_parent'] = $post['ID']; |
|---|
| 204 | + $return['post_status'] = 'inherit'; |
|---|
| 205 | + $return['post_type'] = 'revision'; |
|---|
| 206 | + $return['post_name'] = "$post[ID]-revision"; |
|---|
| 207 | + $return['post_date'] = $post['post_modified']; |
|---|
| 208 | + $return['post_date_gmt'] = $post['post_modified_gmt']; |
|---|
| 209 | + |
|---|
| 210 | + return $return; |
|---|
| 211 | +} |
|---|
| 212 | + |
|---|
| 213 | +/** |
|---|
| 214 | + * wp_save_revision() - Saves an already existing post as a post revision. Typically used immediately prior to post updates. |
|---|
| 215 | + * |
|---|
| 216 | + * @package WordPress |
|---|
| 217 | + * @subpackage Post Revisions |
|---|
| 218 | + * @since 2.6 |
|---|
| 219 | + * |
|---|
| 220 | + * @uses _wp_put_revision() |
|---|
| 221 | + * |
|---|
| 222 | + * @param int $post_id The ID of the post to save as a revision |
|---|
| 223 | + * @return mixed null or 0 if error, new revision ID if success |
|---|
| 224 | + */ |
|---|
| 225 | +function wp_save_revision( $post_id ) { |
|---|
| 226 | + // TODO: rework autosave to use special type of post revision |
|---|
| 227 | + if ( @constant( 'DOING_AUTOSAVE' ) ) |
|---|
| 228 | + return; |
|---|
| 229 | + |
|---|
| 230 | + if ( !$post = get_post( $post_id, ARRAY_A ) ) |
|---|
| 231 | + return; |
|---|
| 232 | + |
|---|
| 233 | + // TODO: open this up for pages also |
|---|
| 234 | + if ( 'post' != $post->post_type ) |
|---|
| 235 | + retun; |
|---|
| 236 | + |
|---|
| 237 | + return _wp_put_revision( $post ); |
|---|
| 238 | +} |
|---|
| 239 | + |
|---|
| 240 | +/** |
|---|
| 241 | + * _wp_put_revision() - Inserts post data into the posts table as a post revision |
|---|
| 242 | + * |
|---|
| 243 | + * @package WordPress |
|---|
| 244 | + * @subpackage Post Revisions |
|---|
| 245 | + * @since 2.6 |
|---|
| 246 | + * |
|---|
| 247 | + * @uses wp_insert_post() |
|---|
| 248 | + * |
|---|
| 249 | + * @param int|object|array $post post ID, post object OR post array |
|---|
| 250 | + * @return mixed null or 0 if error, new revision ID if success |
|---|
| 251 | + */ |
|---|
| 252 | +function _wp_put_revision( $post = null ) { |
|---|
| 253 | + if ( is_object($post) ) |
|---|
| 254 | + $post = get_object_vars( $post ); |
|---|
| 255 | + elseif ( !is_array($post) ) |
|---|
| 256 | + $post = get_post($post, ARRAY_A); |
|---|
| 257 | + |
|---|
| 258 | + if ( !$post || empty($post['ID']) ) |
|---|
| 259 | + return; |
|---|
| 260 | + |
|---|
| 261 | + if ( isset($post['post_type']) && 'revision' == $post_post['type'] ) |
|---|
| 262 | + return new WP_Error( 'post_type', __( 'Cannot create a revision of a revision' ) ); |
|---|
| 263 | + |
|---|
| 264 | + $post = _wp_revision_fields( $post ); |
|---|
| 265 | + |
|---|
| 266 | + if ( $revision_id = wp_insert_post( $post ) ) |
|---|
| 267 | + do_action( '_wp_put_revision', $revision_id ); |
|---|
| 268 | + |
|---|
| 269 | + return $revision_id; |
|---|
| 270 | +} |
|---|
| 271 | + |
|---|
| 272 | +/** |
|---|
| 273 | + * wp_get_revision() - Gets a post revision |
|---|
| 274 | + * |
|---|
| 275 | + * @package WordPress |
|---|
| 276 | + * @subpackage Post Revisions |
|---|
| 277 | + * @since 2.6 |
|---|
| 278 | + * |
|---|
| 279 | + * @uses get_post() |
|---|
| 280 | + * |
|---|
| 281 | + * @param int|object $post post ID or post object |
|---|
| 282 | + * @param $output optional OBJECT, ARRAY_A, or ARRAY_N |
|---|
| 283 | + * @param string $filter optional sanitation filter. @see sanitize_post() |
|---|
| 284 | + * @return mixed null if error or post object if success |
|---|
| 285 | + */ |
|---|
| 286 | +function &wp_get_revision(&$post, $output = OBJECT, $filter = 'raw') { |
|---|
| 287 | + $null = null; |
|---|
| 288 | + if ( !$revision = get_post( $post, OBJECT, $filter ) ) |
|---|
| 289 | + return $revision; |
|---|
| 290 | + if ( 'revision' !== $revision->post_type ) |
|---|
| 291 | + return $null; |
|---|
| 292 | + |
|---|
| 293 | + if ( $output == OBJECT ) { |
|---|
| 294 | + return $revision; |
|---|
| 295 | + } elseif ( $output == ARRAY_A ) { |
|---|
| 296 | + $_revision = get_object_vars($revision); |
|---|
| 297 | + return $_revision; |
|---|
| 298 | + } elseif ( $output == ARRAY_N ) { |
|---|
| 299 | + $_revision = array_values(get_object_vars($revision)); |
|---|
| 300 | + return $_revision; |
|---|
| 301 | + } |
|---|
| 302 | + |
|---|
| 303 | + return $revision; |
|---|
| 304 | +} |
|---|
| 305 | + |
|---|
| 306 | +/** |
|---|
| 307 | + * wp_restore_revision() - Restores a post to the specified revision |
|---|
| 308 | + * |
|---|
| 309 | + * Can restore a past using all fields of the post revision, or only selected fields. |
|---|
| 310 | + * |
|---|
| 311 | + * @package WordPress |
|---|
| 312 | + * @subpackage Post Revisions |
|---|
| 313 | + * @since 2.6 |
|---|
| 314 | + * |
|---|
| 315 | + * @uses wp_get_revision() |
|---|
| 316 | + * @uses wp_update_post() |
|---|
| 317 | + * |
|---|
| 318 | + * @param int|object $revision_id revision ID or revision object |
|---|
| 319 | + * @param array $fields optional What fields to restore from. Defaults to all. |
|---|
| 320 | + * @return mixed null if error, false if no fields to restore, (int) post ID if success |
|---|
| 321 | + */ |
|---|
| 322 | +function wp_restore_revision( $revision_id, $fields = null ) { |
|---|
| 323 | + if ( !$revision = wp_get_revision( $revision_id, ARRAY_A ) ) |
|---|
| 324 | + return $revision; |
|---|
| 325 | + |
|---|
| 326 | + if ( !is_array( $fields ) ) |
|---|
| 327 | + $fields = array_keys( _wp_revision_fields() ); |
|---|
| 328 | + |
|---|
| 329 | + $update = array(); |
|---|
| 330 | + foreach( array_intersect( array_keys( $revision ), $fields ) as $field ) |
|---|
| 331 | + $update[$field] = $revision[$field]; |
|---|
| 332 | + |
|---|
| 333 | + if ( !$update ) |
|---|
| 334 | + return false; |
|---|
| 335 | + |
|---|
| 336 | + $update['ID'] = $revision['post_parent']; |
|---|
| 337 | + |
|---|
| 338 | + if ( $post_id = wp_update_post( $update ) ) |
|---|
| 339 | + do_action( 'wp_restore_revision', $post_id, $revision['ID'] ); |
|---|
| 340 | + |
|---|
| 341 | + return $post_id; |
|---|
| 342 | +} |
|---|
| 343 | + |
|---|
| 344 | +/** |
|---|
| 345 | + * wp_delete_revision() - Deletes a revision. |
|---|
| 346 | + * |
|---|
| 347 | + * Deletes the row from the posts table corresponding to the specified revision |
|---|
| 348 | + * |
|---|
| 349 | + * @package WordPress |
|---|
| 350 | + * @subpackage Post Revisions |
|---|
| 351 | + * @since 2.6 |
|---|
| 352 | + * |
|---|
| 353 | + * @uses wp_get_revision() |
|---|
| 354 | + * @uses wp_delete_post() |
|---|
| 355 | + * |
|---|
| 356 | + * @param int|object $revision_id revision ID or revision object |
|---|
| 357 | + * @param array $fields optional What fields to restore from. Defaults to all. |
|---|
| 358 | + * @return mixed null if error, false if no fields to restore, (int) post ID if success |
|---|
| 359 | + */ |
|---|
| 360 | +function wp_delete_revision( $revision_id ) { |
|---|
| 361 | + if ( !$revision = wp_get_revision( $revision_id ) ) |
|---|
| 362 | + return $revision; |
|---|
| 363 | + |
|---|
| 364 | + if ( $delete = wp_delete_post( $revision->ID ) ) |
|---|
| 365 | + do_action( 'wp_delete_revision', $revision->ID, $revision ); |
|---|
| 366 | + |
|---|
| 367 | + return $delete; |
|---|
| 368 | +} |
|---|
| 369 | + |
|---|
| 370 | +/** |
|---|
| 371 | + * wp_get_post_revisions() - Returns all revisions of specified post |
|---|
| 372 | + * |
|---|
| 373 | + * @package WordPress |
|---|
| 374 | + * @subpackage Post Revisions |
|---|
| 375 | + * @since 2.6 |
|---|
| 376 | + * |
|---|
| 377 | + * @uses get_children() |
|---|
| 378 | + * |
|---|
| 379 | + * @param int|object $post_id post ID or post object |
|---|
| 380 | + * @return array empty if no revisions |
|---|
| 381 | + */ |
|---|
| 382 | +function wp_get_post_revisions( $post_id = 0 ) { |
|---|
| 383 | + if ( ( !$post = get_post( $post_id ) ) || empty( $post->ID ) ) |
|---|
| 384 | + return array(); |
|---|
| 385 | + |
|---|
| 386 | + if ( !$revisions = get_children( array( 'post_parent' => $post->ID, 'post_type' => 'revision' ) ) ) |
|---|
| 387 | + return array(); |
|---|
| 388 | + return $revisions; |
|---|
| 389 | +} |
|---|
| 390 | + |
|---|
| 391 | ?> |
|---|
| 392 | Index: wp-includes/wp-diff.php |
|---|
| 393 | =================================================================== |
|---|
| 394 | --- wp-includes/wp-diff.php (revision 0) |
|---|
| 395 | +++ wp-includes/wp-diff.php (revision 0) |
|---|
| 396 | @@ -0,0 +1,318 @@ |
|---|
| 397 | +<?php |
|---|
| 398 | + |
|---|
| 399 | +if ( !class_exists( 'Text_Diff' ) ) { |
|---|
| 400 | + require( 'Text/Diff.php' ); |
|---|
| 401 | + require( 'Text/Diff/Renderer.php' ); |
|---|
| 402 | + require( 'Text/Diff/Renderer/inline.php' ); |
|---|
| 403 | +} |
|---|
| 404 | + |
|---|
| 405 | + |
|---|
| 406 | +/* Descendent of a bastard child of piece of an old MediaWiki Diff Formatter |
|---|
| 407 | + * |
|---|
| 408 | + * Basically all that remains is the table structure and some method names. |
|---|
| 409 | + */ |
|---|
| 410 | + |
|---|
| 411 | +class WP_Text_Diff_Renderer_Table extends Text_Diff_Renderer { |
|---|
| 412 | + var $_leading_context_lines = 10000; |
|---|
| 413 | + var $_trailing_context_lines = 10000; |
|---|
| 414 | + var $_diff_threshold = 0.6; |
|---|
| 415 | + |
|---|
| 416 | + var $inline_diff_renderer = 'WP_Text_Diff_Renderer_inline'; |
|---|
| 417 | + |
|---|
| 418 | + function Text_Diff_Renderer_Table( $params = array() ) { |
|---|
| 419 | + $parent = get_parent_class($this); |
|---|
| 420 | + $this->$parent( $params ); |
|---|
| 421 | + } |
|---|
| 422 | + |
|---|
| 423 | + function _startBlock( $header ) { |
|---|
| 424 | + return ''; |
|---|
| 425 | + } |
|---|
| 426 | + |
|---|
| 427 | + function _lines( $lines, $prefix=' ' ) { |
|---|
| 428 | + } |
|---|
| 429 | + |
|---|
| 430 | + // HTML-escape parameter before calling this |
|---|
| 431 | + function addedLine( $line ) { |
|---|
| 432 | + return "<td>+</td><td class='diff-addedline'>{$line}</td>"; |
|---|
| 433 | + } |
|---|
| 434 | + |
|---|
| 435 | + // HTML-escape parameter before calling this |
|---|
| 436 | + function deletedLine( $line ) { |
|---|
| 437 | + return "<td>-</td><td class='diff-deletedline'>{$line}</td>"; |
|---|
| 438 | + } |
|---|
| 439 | + |
|---|
| 440 | + // HTML-escape parameter before calling this |
|---|
| 441 | + function contextLine( $line ) { |
|---|
| 442 | + return "<td> </td><td class='diff-context'>{$line}</td>"; |
|---|
| 443 | + } |
|---|
| 444 | + |
|---|
| 445 | + function emptyLine() { |
|---|
| 446 | + return '<td colspan="2"> </td>'; |
|---|
| 447 | + } |
|---|
| 448 | + |
|---|
| 449 | + function _added( $lines, $encode = true ) { |
|---|
| 450 | + $r = ''; |
|---|
| 451 | + foreach ($lines as $line) { |
|---|
| 452 | + if ( $encode ) |
|---|
| 453 | + $line = htmlspecialchars( $line ); |
|---|
| 454 | + $r .= '<tr>' . $this->emptyLine() . $this->addedLine( $line ) . "</tr>\n"; |
|---|
| 455 | + } |
|---|
| 456 | + return $r; |
|---|
| 457 | + } |
|---|
| 458 | + |
|---|
| 459 | + function _deleted( $lines, $encode = true ) { |
|---|
| 460 | + $r = ''; |
|---|
| 461 | + foreach ($lines as $line) { |
|---|
| 462 | + if ( $encode ) |
|---|
| 463 | + $line = htmlspecialchars( $line ); |
|---|
| 464 | + $r .= '<tr>' . $this->deletedLine( $line ) . $this->emptyLine() . "</tr>\n"; |
|---|
| 465 | + } |
|---|
| 466 | + return $r; |
|---|
| 467 | + } |
|---|
| 468 | + |
|---|
| 469 | + function _context( $lines, $encode = true ) { |
|---|
| 470 | + $r = ''; |
|---|
| 471 | + foreach ($lines as $line) { |
|---|
| 472 | + if ( $encode ) |
|---|
| 473 | + $line = htmlspecialchars( $line ); |
|---|
| 474 | + $r .= '<tr>' . |
|---|
| 475 | + $this->contextLine( $line ) . $this->contextLine( $line ) . "</tr>\n"; |
|---|
| 476 | + } |
|---|
| 477 | + return $r; |
|---|
| 478 | + } |
|---|
| 479 | + |
|---|
| 480 | + // Process changed lines to do word-by-word diffs for extra highlighting (TRAC style) |
|---|
| 481 | + // sometimes these lines can actually be deleted or added rows - we do additional processing |
|---|
| 482 | + // to figure that out |
|---|
| 483 | + function _changed( $orig, $final ) { |
|---|
| 484 | + $r = ''; |
|---|
| 485 | + |
|---|
| 486 | + // Does the aforementioned additional processing |
|---|
| 487 | + // *_matches tell what rows are "the same" in orig and final. Those pairs will be diffed to get word changes |
|---|
| 488 | + // match is numeric: an index in other column |
|---|
| 489 | + // match is 'X': no match. It is a new row |
|---|
| 490 | + // *_rows are column vectors for the orig column and the final column. |
|---|
| 491 | + // row >= 0: an indix of the $orig or $final array |
|---|
| 492 | + // row < 0: a blank row for that column |
|---|
| 493 | + list($orig_matches, $final_matches, $orig_rows, $final_rows) = $this->interleave_changed_lines( $orig, $final ); |
|---|
| 494 | + |
|---|
| 495 | + |
|---|
| 496 | + // These will hold the word changes as determined by an inline diff |
|---|
| 497 | + $orig_diffs = array(); |
|---|
| 498 | + $final_diffs = array(); |
|---|
| 499 | + |
|---|
| 500 | + // Compute word diffs for each matched pair using the inline diff |
|---|
| 501 | + foreach ( $orig_matches as $o => $f ) { |
|---|
| 502 | + if ( is_numeric($o) && is_numeric($f) ) { |
|---|
| 503 | + $text_diff = new Text_Diff( 'auto', array( array($orig[$o]), array($final[$f]) ) ); |
|---|
| 504 | + $renderer = new $this->inline_diff_renderer; |
|---|
| 505 | + $diff = $renderer->render( $text_diff ); |
|---|
| 506 | + |
|---|
| 507 | + // If they're too different, don't include any <ins> or <dels> |
|---|
| 508 | + if ( $diff_count = preg_match_all( '!(<ins>.*?</ins>|<del>.*?</del>)!', $diff, $diff_matches ) ) { |
|---|
| 509 | + // length of all text between <ins> or <del> |
|---|
| 510 | + $stripped_matches = strlen(strip_tags( join(' ', $diff_matches[0]) )); |
|---|
| 511 | + // since we count lengith of text between <ins> or <del> (instead of picking just one), |
|---|
| 512 | + // we double the length of chars not in those tags. |
|---|
| 513 | + $stripped_diff = strlen(strip_tags( $diff )) * 2 - $stripped_matches; |
|---|
| 514 | + $diff_ratio = $stripped_matches / $stripped_diff; |
|---|
| 515 | + if ( $diff_ratio > $this->_diff_threshold ) |
|---|
| 516 | + continue; // Too different. Don't save diffs. |
|---|
| 517 | + } |
|---|
| 518 | + |
|---|
| 519 | + // Un-inline the diffs by removing del or ins |
|---|
| 520 | + $orig_diffs[$o] = preg_replace( '|<ins>.*?</ins>|', '', $diff ); |
|---|
| 521 | + $final_diffs[$f] = preg_replace( '|<del>.*?</del>|', '', $diff ); |
|---|
| 522 | + } |
|---|
| 523 | + } |
|---|
| 524 | + |
|---|
| 525 | + foreach ( array_keys($orig_rows) as $row ) { |
|---|
| 526 | + // Both columns have blanks. Ignore them. |
|---|
| 527 | + if ( $orig_rows[$row] < 0 && $final_rows[$row] < 0 ) |
|---|
| 528 | + continue; |
|---|
| 529 | + |
|---|
| 530 | + // If we have a word based diff, use it. Otherwise, use the normal line. |
|---|
| 531 | + $orig_line = isset($orig_diffs[$orig_rows[$row]]) |
|---|
| 532 | + ? $orig_diffs[$orig_rows[$row]] |
|---|
| 533 | + : htmlspecialchars($orig[$orig_rows[$row]]); |
|---|
| 534 | + $final_line = isset($final_diffs[$final_rows[$row]]) |
|---|
| 535 | + ? $final_diffs[$final_rows[$row]] |
|---|
| 536 | + : htmlspecialchars($final[$final_rows[$row]]); |
|---|
| 537 | + |
|---|
| 538 | + if ( $orig_rows[$row] < 0 ) { // Orig is blank. This is really an added row. |
|---|
| 539 | + $r .= $this->_added( array($final_line), false ); |
|---|
| 540 | + } elseif ( $final_rows[$row] < 0 ) { // Final is blank. This is really a deleted row. |
|---|
| 541 | + $r .= $this->_deleted( array($orig_line), false ); |
|---|
| 542 | + } else { // A true changed row. |
|---|
| 543 | + $r .= '<tr>' . $this->deletedLine( $orig_line ) . $this->addedLine( $final_line ) . "</tr>\n"; |
|---|
| 544 | + } |
|---|
| 545 | + } |
|---|
| 546 | + |
|---|
| 547 | + return $r; |
|---|
| 548 | + } |
|---|
| 549 | + |
|---|
| 550 | + // Takes changed blocks and matches which rows in orig turned into which rows in final. |
|---|
| 551 | + // Returns |
|---|
| 552 | + // *_matches ( which rows match with which ) |
|---|
| 553 | + // *_rows ( order of rows in each column interleaved with blank rows as necessary ) |
|---|
| 554 | + function interleave_changed_lines( $orig, $final ) { |
|---|
| 555 | + |
|---|
| 556 | + // Contains all pairwise string comparisons. Keys are such that this need only be a one dimensional array. |
|---|
| 557 | + $matches = array(); |
|---|
| 558 | + foreach ( array_keys($orig) as $o ) { |
|---|
| 559 | + foreach ( array_keys($final) as $f ) { |
|---|
| 560 | + $matches["$o,$f"] = $this->compute_string_distance( $orig[$o], $final[$f] ); |
|---|
| 561 | + } |
|---|
| 562 | + } |
|---|
| 563 | + asort($matches); // Order by string distance. |
|---|
| 564 | + |
|---|
| 565 | + $orig_matches = array(); |
|---|
| 566 | + $final_matches = array(); |
|---|
| 567 | + |
|---|
| 568 | + foreach ( $matches as $keys => $difference ) { |
|---|
| 569 | + list($o, $f) = explode(',', $keys); |
|---|
| 570 | + $o = (int) $o; |
|---|
| 571 | + $f = (int) $f; |
|---|
| 572 | + |
|---|
| 573 | + // Already have better matches for these guys |
|---|
| 574 | + if ( isset($orig_matches[$o]) && isset($final_matches[$f]) ) |
|---|
| 575 | + continue; |
|---|
| 576 | + |
|---|
| 577 | + // First match for these guys. Must be best match |
|---|
| 578 | + if ( !isset($orig_matches[$o]) && !isset($final_matches[$f]) ) { |
|---|
| 579 | + $orig_matches[$o] = $f; |
|---|
| 580 | + $final_matches[$f] = $o; |
|---|
| 581 | + continue; |
|---|
| 582 | + } |
|---|
| 583 | + |
|---|
| 584 | + // Best match of this final is already taken? Must mean this final is a new row. |
|---|
| 585 | + if ( isset($orig_matches[$o]) ) |
|---|
| 586 | + $final_matches[$f] = 'x'; |
|---|
| 587 | + |
|---|
| 588 | + // Best match of this orig is already taken? Must mean this orig is a deleted row. |
|---|
| 589 | + elseif ( isset($final_matches[$f]) ) |
|---|
| 590 | + $orig_matches[$o] = 'x'; |
|---|
| 591 | + } |
|---|
| 592 | + |
|---|
| 593 | + // We read the text in this order |
|---|
| 594 | + ksort($orig_matches); |
|---|
| 595 | + ksort($final_matches); |
|---|
| 596 | + |
|---|
| 597 | + |
|---|
| 598 | + // Stores rows and blanks for each column. |
|---|
| 599 | + $orig_rows = $orig_rows_copy = array_keys($orig_matches); |
|---|
| 600 | + $final_rows = array_keys($final_matches); |
|---|
| 601 | + |
|---|
| 602 | + // Interleaves rows with blanks to keep matches aligned. |
|---|
| 603 | + // We may end up with some extraneous blank rows, but we'll just ignore them later. |
|---|
| 604 | + foreach ( $orig_rows_copy as $orig_row ) { |
|---|
| 605 | + $final_pos = array_search($orig_matches[$orig_row], $final_rows, true); |
|---|
| 606 | + $orig_pos = (int) array_search($orig_row, $orig_rows, true); |
|---|
| 607 | + |
|---|
| 608 | + if ( false === $final_pos ) { // This orig is paired with a blank final. |
|---|
| 609 | + array_splice( $final_rows, $orig_pos, 0, -1 ); |
|---|
| 610 | + } elseif ( $final_pos < $orig_pos ) { // This orig's match is up a ways. Pad final with blank rows. |
|---|
| 611 | + $diff_pos = $final_pos - $orig_pos; |
|---|
| 612 | + while ( $diff_pos < 0 ) |
|---|
| 613 | + array_splice( $final_rows, $orig_pos, 0, $diff_pos++ ); |
|---|
| 614 | + } elseif ( $final_pos > $orig_pos ) { // This orig's match is down a ways. Pad orig with blank rows. |
|---|
| 615 | + $diff_pos = $orig_pos - $final_pos; |
|---|
| 616 | + while ( $diff_pos < 0 ) |
|---|
| 617 | + array_splice( $orig_rows, $orig_pos, 0, $diff_pos++ ); |
|---|
| 618 | + } |
|---|
| 619 | + } |
|---|
| 620 | + |
|---|
| 621 | + |
|---|
| 622 | + // Pad the ends with blank rows if the columns aren't the same length |
|---|
| 623 | + $diff_count = count($orig_rows) - count($final_rows); |
|---|
| 624 | + if ( $diff_count < 0 ) { |
|---|
| 625 | + while ( $diff_count < 0 ) |
|---|
| 626 | + array_push($orig_rows, $diff_count++); |
|---|
| 627 | + } elseif ( $diff_count > 0 ) { |
|---|
| 628 | + $diff_count = -1 * $diff_count; |
|---|
| 629 | + while ( $diff_count < 0 ) |
|---|
| 630 | + array_push($final_rows, $diff_count++); |
|---|
| 631 | + } |
|---|
| 632 | + |
|---|
| 633 | + return array($orig_matches, $final_matches, $orig_rows, $final_rows); |
|---|
| 634 | + |
|---|
| 635 | +/* |
|---|
| 636 | + // Debug |
|---|
| 637 | + echo "\n\n\n\n\n"; |
|---|
| 638 | + |
|---|
| 639 | + echo "-- DEBUG Matches: Orig -> Final --"; |
|---|
| 640 | + |
|---|
| 641 | + foreach ( $orig_matches as $o => $f ) { |
|---|
| 642 | + echo "\n\n\n\n\n"; |
|---|
| 643 | + echo "ORIG: $o, FINAL: $f\n"; |
|---|
| 644 | + var_dump($orig[$o],$final[$f]); |
|---|
| 645 | + } |
|---|
| 646 | + echo "\n\n\n\n\n"; |
|---|
| 647 | + |
|---|
| 648 | + echo "-- DEBUG Matches: Final -> Orig --"; |
|---|
| 649 | + |
|---|
| 650 | + foreach ( $final_matches as $f => $o ) { |
|---|
| 651 | + echo "\n\n\n\n\n"; |
|---|
| 652 | + echo "FINAL: $f, ORIG: $o\n"; |
|---|
| 653 | + var_dump($final[$f],$orig[$o]); |
|---|
| 654 | + } |
|---|
| 655 | + echo "\n\n\n\n\n"; |
|---|
| 656 | + |
|---|
| 657 | + echo "-- DEBUG Rows: Orig -- Final --"; |
|---|
| 658 | + |
|---|
| 659 | + echo "\n\n\n\n\n"; |
|---|
| 660 | + foreach ( $orig_rows as $row => $o ) { |
|---|
| 661 | + if ( $o < 0 ) |
|---|
| 662 | + $o = 'X'; |
|---|
| 663 | + $f = $final_rows[$row]; |
|---|
| 664 | + if ( $f < 0 ) |
|---|
| 665 | + $f = 'X'; |
|---|
| 666 | + echo "$o -- $f\n"; |
|---|
| 667 | + } |
|---|
| 668 | + echo "\n\n\n\n\n"; |
|---|
| 669 | + |
|---|
| 670 | + echo "-- END DEBUG --"; |
|---|
| 671 | + |
|---|
| 672 | + echo "\n\n\n\n\n"; |
|---|
| 673 | + |
|---|
| 674 | + return array($orig_matches, $final_matches, $orig_rows, $final_rows); |
|---|
| 675 | +*/ |
|---|
| 676 | + } |
|---|
| 677 | + |
|---|
| 678 | + |
|---|
| 679 | + // Computes a number that is intended to reflect the "distance" between two strings. |
|---|
| 680 | + function compute_string_distance( $string1, $string2 ) { |
|---|
| 681 | + // Vectors containing character frequency for all chars in each string |
|---|
| 682 | + $chars1 = count_chars($string1); |
|---|
| 683 | + $chars2 = count_chars($string2); |
|---|
| 684 | + |
|---|
| 685 | + // L1-norm of difference vector. |
|---|
| 686 | + $difference = array_sum( array_map( array(&$this, 'difference'), $chars1, $chars2 ) ); |
|---|
| 687 | + |
|---|
| 688 | + // $string1 has zero length? Odd. Give huge penalty by not dividing. |
|---|
| 689 | + if ( !$string1 ) |
|---|
| 690 | + return $difference; |
|---|
| 691 | + |
|---|
| 692 | + // Return distance per charcter (of string1) |
|---|
| 693 | + return $difference / strlen($string1); |
|---|
| 694 | + } |
|---|
| 695 | + |
|---|
| 696 | + function difference( $a, $b ) { |
|---|
| 697 | + return abs( $a - $b ); |
|---|
| 698 | + } |
|---|
| 699 | + |
|---|
| 700 | +} |
|---|
| 701 | + |
|---|
| 702 | +// Better word splitting than the PEAR package provides |
|---|
| 703 | +class WP_Text_Diff_Renderer_inline extends Text_Diff_Renderer_inline { |
|---|
| 704 | + |
|---|
| 705 | + function _splitOnWords($string, $newlineEscape = "\n") { |
|---|
| 706 | + $string = str_replace("\0", '', $string); |
|---|
| 707 | + $words = preg_split( '/([^\w])/', $string, -1, PREG_SPLIT_DELIM_CAPTURE ); |
|---|
| 708 | + $words = str_replace( "\n", $newlineEscape, $words ); |
|---|
| 709 | + return $words; |
|---|
| 710 | + } |
|---|
| 711 | + |
|---|
| 712 | +} |
|---|
| 713 | + |
|---|
| 714 | +?> |
|---|
| 715 | Index: wp-includes/link-template.php |
|---|
| 716 | =================================================================== |
|---|
| 717 | --- wp-includes/link-template.php (revision 7746) |
|---|
| 718 | +++ wp-includes/link-template.php (working copy) |
|---|
| 719 | @@ -442,10 +442,15 @@ |
|---|
| 720 | return $link; |
|---|
| 721 | } |
|---|
| 722 | |
|---|
| 723 | -function get_edit_post_link( $id = 0 ) { |
|---|
| 724 | +function get_edit_post_link( $id = 0, $context = 'display' ) { |
|---|
| 725 | if ( !$post = &get_post( $id ) ) |
|---|
| 726 | return; |
|---|
| 727 | |
|---|
| 728 | + if ( 'display' == $context ) |
|---|
| 729 | + $action = 'action=edit&'; |
|---|
| 730 | + else |
|---|
| 731 | + $action = 'action=edit&'; |
|---|
| 732 | + |
|---|
| 733 | switch ( $post->post_type ) : |
|---|
| 734 | case 'page' : |
|---|
| 735 | if ( !current_user_can( 'edit_page', $post->ID ) ) |
|---|
| 736 | @@ -459,6 +464,13 @@ |
|---|
| 737 | $file = 'media'; |
|---|
| 738 | $var = 'attachment_id'; |
|---|
| 739 | break; |
|---|
| 740 | + case 'revision' : |
|---|
| 741 | + if ( !current_user_can( 'edit_post', $post->ID ) ) |
|---|
| 742 | + return; |
|---|
| 743 | + $file = 'revision'; |
|---|
| 744 | + $var = 'revision'; |
|---|
| 745 | + $action = ''; |
|---|
| 746 | + break; |
|---|
| 747 | default : |
|---|
| 748 | if ( !current_user_can( 'edit_post', $post->ID ) ) |
|---|
| 749 | return; |
|---|
| 750 | @@ -467,7 +479,7 @@ |
|---|
| 751 | break; |
|---|
| 752 | endswitch; |
|---|
| 753 | |
|---|
| 754 | - return apply_filters( 'get_edit_post_link', get_bloginfo( 'wpurl' ) . "/wp-admin/$file.php?action=edit&$var=$post->ID", $post->ID ); |
|---|
| 755 | + return apply_filters( 'get_edit_post_link', get_bloginfo( 'wpurl' ) . "/wp-admin/$file.php?{$action}$var=$post->ID", $post->ID ); |
|---|
| 756 | } |
|---|
| 757 | |
|---|
| 758 | function edit_post_link( $link = 'Edit This', $before = '', $after = '' ) { |
|---|
| 759 | Index: wp-includes/Text/Diff/Engine/xdiff.php |
|---|
| 760 | =================================================================== |
|---|
| 761 | --- wp-includes/Text/Diff/Engine/xdiff.php (revision 0) |
|---|
| 762 | +++ wp-includes/Text/Diff/Engine/xdiff.php (revision 0) |
|---|
| 763 | @@ -0,0 +1,63 @@ |
|---|
| 764 | +<?php |
|---|
| 765 | +/** |
|---|
| 766 | + * Class used internally by Diff to actually compute the diffs. |
|---|
| 767 | + * |
|---|
| 768 | + * This class uses the xdiff PECL package (http://pecl.php.net/package/xdiff) |
|---|
| 769 | + * to compute the differences between the two input arrays. |
|---|
| 770 | + * |
|---|
| 771 | + * $Horde: framework/Text_Diff/Diff/Engine/xdiff.php,v 1.6 2008/01/04 10:07:50 jan Exp $ |
|---|
| 772 | + * |
|---|
| 773 | + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) |
|---|
| 774 | + * |
|---|
| 775 | + * See the enclosed file COPYING for license information (LGPL). If you did |
|---|
| 776 | + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. |
|---|
| 777 | + * |
|---|
| 778 | + * @author Jon Parise <jon@horde.org> |
|---|
| 779 | + * @package Text_Diff |
|---|
| 780 | + */ |
|---|
| 781 | +class Text_Diff_Engine_xdiff { |
|---|
| 782 | + |
|---|
| 783 | + /** |
|---|
| 784 | + */ |
|---|
| 785 | + function diff($from_lines, $to_lines) |
|---|
| 786 | + { |
|---|
| 787 | + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); |
|---|
| 788 | + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); |
|---|
| 789 | + |
|---|
| 790 | + /* Convert the two input arrays into strings for xdiff processing. */ |
|---|
| 791 | + $from_string = implode("\n", $from_lines); |
|---|
| 792 | + $to_string = implode("\n", $to_lines); |
|---|
| 793 | + |
|---|
| 794 | + /* Diff the two strings and convert the result to an array. */ |
|---|
| 795 | + $diff = xdiff_string_diff($from_string, $to_string, count($to_lines)); |
|---|
| 796 | + $diff = explode("\n", $diff); |
|---|
| 797 | + |
|---|
| 798 | + /* Walk through the diff one line at a time. We build the $edits |
|---|
| 799 | + * array of diff operations by reading the first character of the |
|---|
| 800 | + * xdiff output (which is in the "unified diff" format). |
|---|
| 801 | + * |
|---|
| 802 | + * Note that we don't have enough information to detect "changed" |
|---|
| 803 | + * lines using this approach, so we can't add Text_Diff_Op_changed |
|---|
| 804 | + * instances to the $edits array. The result is still perfectly |
|---|
| 805 | + * valid, albeit a little less descriptive and efficient. */ |
|---|
| 806 | + $edits = array(); |
|---|
| 807 | + foreach ($diff as $line) { |
|---|
| 808 | + switch ($line[0]) { |
|---|
| 809 | + case ' ': |
|---|
| 810 | + $edits[] = &new Text_Diff_Op_copy(array(substr($line, 1))); |
|---|
| 811 | + break; |
|---|
| 812 | + |
|---|
| 813 | + case '+': |
|---|
| 814 | + $edits[] = &new Text_Diff_Op_add(array(substr($line, 1))); |
|---|
| 815 | + break; |
|---|
| 816 | + |
|---|
| 817 | + case '-': |
|---|
| 818 | + $edits[] = &new Text_Diff_Op_delete(array(substr($line, 1))); |
|---|
| 819 | + break; |
|---|
| 820 | + } |
|---|
| 821 | + } |
|---|
| 822 | + |
|---|
| 823 | + return $edits; |
|---|
| 824 | + } |
|---|
| 825 | + |
|---|
| 826 | +} |
|---|
| 827 | Index: wp-includes/Text/Diff/Engine/native.php |
|---|
| 828 | =================================================================== |
|---|
| 829 | --- wp-includes/Text/Diff/Engine/native.php (revision 0) |
|---|
| 830 | +++ wp-includes/Text/Diff/Engine/native.php (revision 0) |
|---|
| 831 | @@ -0,0 +1,437 @@ |
|---|
| 832 | +<?php |
|---|
| 833 | +/** |
|---|
| 834 | + * $Horde: framework/Text_Diff/Diff/Engine/native.php,v 1.10 2008/01/04 10:27:53 jan Exp $ |
|---|
| 835 | + * |
|---|
| 836 | + * Class used internally by Text_Diff to actually compute the diffs. This |
|---|
| 837 | + * class is implemented using native PHP code. |
|---|
| 838 | + * |
|---|
| 839 | + * The algorithm used here is mostly lifted from the perl module |
|---|
| 840 | + * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: |
|---|
| 841 | + * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip |
|---|
| 842 | + * |
|---|
| 843 | + * More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html |
|---|
| 844 | + * |
|---|
| 845 | + * Some ideas (and a bit of code) are taken from analyze.c, of GNU |
|---|
| 846 | + * diffutils-2.7, which can be found at: |
|---|
| 847 | + * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz |
|---|
| 848 | + * |
|---|
| 849 | + * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from |
|---|
| 850 | + * Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this |
|---|
| 851 | + * code was written by him, and is used/adapted with his permission. |
|---|
| 852 | + * |
|---|
| 853 | + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) |
|---|
| 854 | + * |
|---|
| 855 | + * See the enclosed file COPYING for license information (LGPL). If you did |
|---|
| 856 | + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. |
|---|
| 857 | + * |
|---|
| 858 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 859 | + * @package Text_Diff |
|---|
| 860 | + */ |
|---|
| 861 | +class Text_Diff_Engine_native { |
|---|
| 862 | + |
|---|
| 863 | + function diff($from_lines, $to_lines) |
|---|
| 864 | + { |
|---|
| 865 | + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); |
|---|
| 866 | + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); |
|---|
| 867 | + |
|---|
| 868 | + $n_from = count($from_lines); |
|---|
| 869 | + $n_to = count($to_lines); |
|---|
| 870 | + |
|---|
| 871 | + $this->xchanged = $this->ychanged = array(); |
|---|
| 872 | + $this->xv = $this->yv = array(); |
|---|
| 873 | + $this->xind = $this->yind = array(); |
|---|
| 874 | + unset($this->seq); |
|---|
| 875 | + unset($this->in_seq); |
|---|
| 876 | + unset($this->lcs); |
|---|
| 877 | + |
|---|
| 878 | + // Skip leading common lines. |
|---|
| 879 | + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { |
|---|
| 880 | + if ($from_lines[$skip] !== $to_lines[$skip]) { |
|---|
| 881 | + break; |
|---|
| 882 | + } |
|---|
| 883 | + $this->xchanged[$skip] = $this->ychanged[$skip] = false; |
|---|
| 884 | + } |
|---|
| 885 | + |
|---|
| 886 | + // Skip trailing common lines. |
|---|
| 887 | + $xi = $n_from; $yi = $n_to; |
|---|
| 888 | + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { |
|---|
| 889 | + if ($from_lines[$xi] !== $to_lines[$yi]) { |
|---|
| 890 | + break; |
|---|
| 891 | + } |
|---|
| 892 | + $this->xchanged[$xi] = $this->ychanged[$yi] = false; |
|---|
| 893 | + } |
|---|
| 894 | + |
|---|
| 895 | + // Ignore lines which do not exist in both files. |
|---|
| 896 | + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { |
|---|
| 897 | + $xhash[$from_lines[$xi]] = 1; |
|---|
| 898 | + } |
|---|
| 899 | + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { |
|---|
| 900 | + $line = $to_lines[$yi]; |
|---|
| 901 | + if (($this->ychanged[$yi] = empty($xhash[$line]))) { |
|---|
| 902 | + continue; |
|---|
| 903 | + } |
|---|
| 904 | + $yhash[$line] = 1; |
|---|
| 905 | + $this->yv[] = $line; |
|---|
| 906 | + $this->yind[] = $yi; |
|---|
| 907 | + } |
|---|
| 908 | + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { |
|---|
| 909 | + $line = $from_lines[$xi]; |
|---|
| 910 | + if (($this->xchanged[$xi] = empty($yhash[$line]))) { |
|---|
| 911 | + continue; |
|---|
| 912 | + } |
|---|
| 913 | + $this->xv[] = $line; |
|---|
| 914 | + $this->xind[] = $xi; |
|---|
| 915 | + } |
|---|
| 916 | + |
|---|
| 917 | + // Find the LCS. |
|---|
| 918 | + $this->_compareseq(0, count($this->xv), 0, count($this->yv)); |
|---|
| 919 | + |
|---|
| 920 | + // Merge edits when possible. |
|---|
| 921 | + $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged); |
|---|
| 922 | + $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged); |
|---|
| 923 | + |
|---|
| 924 | + // Compute the edit operations. |
|---|
| 925 | + $edits = array(); |
|---|
| 926 | + $xi = $yi = 0; |
|---|
| 927 | + while ($xi < $n_from || $yi < $n_to) { |
|---|
| 928 | + assert($yi < $n_to || $this->xchanged[$xi]); |
|---|
| 929 | + assert($xi < $n_from || $this->ychanged[$yi]); |
|---|
| 930 | + |
|---|
| 931 | + // Skip matching "snake". |
|---|
| 932 | + $copy = array(); |
|---|
| 933 | + while ($xi < $n_from && $yi < $n_to |
|---|
| 934 | + && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { |
|---|
| 935 | + $copy[] = $from_lines[$xi++]; |
|---|
| 936 | + ++$yi; |
|---|
| 937 | + } |
|---|
| 938 | + if ($copy) { |
|---|
| 939 | + $edits[] = &new Text_Diff_Op_copy($copy); |
|---|
| 940 | + } |
|---|
| 941 | + |
|---|
| 942 | + // Find deletes & adds. |
|---|
| 943 | + $delete = array(); |
|---|
| 944 | + while ($xi < $n_from && $this->xchanged[$xi]) { |
|---|
| 945 | + $delete[] = $from_lines[$xi++]; |
|---|
| 946 | + } |
|---|
| 947 | + |
|---|
| 948 | + $add = array(); |
|---|
| 949 | + while ($yi < $n_to && $this->ychanged[$yi]) { |
|---|
| 950 | + $add[] = $to_lines[$yi++]; |
|---|
| 951 | + } |
|---|
| 952 | + |
|---|
| 953 | + if ($delete && $add) { |
|---|
| 954 | + $edits[] = &new Text_Diff_Op_change($delete, $add); |
|---|
| 955 | + } elseif ($delete) { |
|---|
| 956 | + $edits[] = &new Text_Diff_Op_delete($delete); |
|---|
| 957 | + } elseif ($add) { |
|---|
| 958 | + $edits[] = &new Text_Diff_Op_add($add); |
|---|
| 959 | + } |
|---|
| 960 | + } |
|---|
| 961 | + |
|---|
| 962 | + return $edits; |
|---|
| 963 | + } |
|---|
| 964 | + |
|---|
| 965 | + /** |
|---|
| 966 | + * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, |
|---|
| 967 | + * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized |
|---|
| 968 | + * segments. |
|---|
| 969 | + * |
|---|
| 970 | + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of |
|---|
| 971 | + * NCHUNKS+1 (X, Y) indexes giving the diving points between sub |
|---|
| 972 | + * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), |
|---|
| 973 | + * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == |
|---|
| 974 | + * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). |
|---|
| 975 | + * |
|---|
| 976 | + * This function assumes that the first lines of the specified portions of |
|---|
| 977 | + * the two files do not match, and likewise that the last lines do not |
|---|
| 978 | + * match. The caller must trim matching lines from the beginning and end |
|---|
| 979 | + * of the portions it is going to specify. |
|---|
| 980 | + */ |
|---|
| 981 | + function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) |
|---|
| 982 | + { |
|---|
| 983 | + $flip = false; |
|---|
| 984 | + |
|---|
| 985 | + if ($xlim - $xoff > $ylim - $yoff) { |
|---|
| 986 | + /* Things seems faster (I'm not sure I understand why) when the |
|---|
| 987 | + * shortest sequence is in X. */ |
|---|
| 988 | + $flip = true; |
|---|
| 989 | + list ($xoff, $xlim, $yoff, $ylim) |
|---|
| 990 | + = array($yoff, $ylim, $xoff, $xlim); |
|---|
| 991 | + } |
|---|
| 992 | + |
|---|
| 993 | + if ($flip) { |
|---|
| 994 | + for ($i = $ylim - 1; $i >= $yoff; $i--) { |
|---|
| 995 | + $ymatches[$this->xv[$i]][] = $i; |
|---|
| 996 | + } |
|---|
| 997 | + } else { |
|---|
| 998 | + for ($i = $ylim - 1; $i >= $yoff; $i--) { |
|---|
| 999 | + $ymatches[$this->yv[$i]][] = $i; |
|---|
| 1000 | + } |
|---|
| 1001 | + } |
|---|
| 1002 | + |
|---|
| 1003 | + $this->lcs = 0; |
|---|
| 1004 | + $this->seq[0]= $yoff - 1; |
|---|
| 1005 | + $this->in_seq = array(); |
|---|
| 1006 | + $ymids[0] = array(); |
|---|
| 1007 | + |
|---|
| 1008 | + $numer = $xlim - $xoff + $nchunks - 1; |
|---|
| 1009 | + $x = $xoff; |
|---|
| 1010 | + for ($chunk = 0; $chunk < $nchunks; $chunk++) { |
|---|
| 1011 | + if ($chunk > 0) { |
|---|
| 1012 | + for ($i = 0; $i <= $this->lcs; $i++) { |
|---|
| 1013 | + $ymids[$i][$chunk - 1] = $this->seq[$i]; |
|---|
| 1014 | + } |
|---|
| 1015 | + } |
|---|
| 1016 | + |
|---|
| 1017 | + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); |
|---|
| 1018 | + for (; $x < $x1; $x++) { |
|---|
| 1019 | + $line = $flip ? $this->yv[$x] : $this->xv[$x]; |
|---|
| 1020 | + if (empty($ymatches[$line])) { |
|---|
| 1021 | + continue; |
|---|
| 1022 | + } |
|---|
| 1023 | + $matches = $ymatches[$line]; |
|---|
| 1024 | + reset($matches); |
|---|
| 1025 | + while (list(, $y) = each($matches)) { |
|---|
| 1026 | + if (empty($this->in_seq[$y])) { |
|---|
| 1027 | + $k = $this->_lcsPos($y); |
|---|
| 1028 | + assert($k > 0); |
|---|
| 1029 | + $ymids[$k] = $ymids[$k - 1]; |
|---|
| 1030 | + break; |
|---|
| 1031 | + } |
|---|
| 1032 | + } |
|---|
| 1033 | + while (list(, $y) = each($matches)) { |
|---|
| 1034 | + if ($y > $this->seq[$k - 1]) { |
|---|
| 1035 | + assert($y <= $this->seq[$k]); |
|---|
| 1036 | + /* Optimization: this is a common case: next match is |
|---|
| 1037 | + * just replacing previous match. */ |
|---|
| 1038 | + $this->in_seq[$this->seq[$k]] = false; |
|---|
| 1039 | + $this->seq[$k] = $y; |
|---|
| 1040 | + $this->in_seq[$y] = 1; |
|---|
| 1041 | + } elseif (empty($this->in_seq[$y])) { |
|---|
| 1042 | + $k = $this->_lcsPos($y); |
|---|
| 1043 | + assert($k > 0); |
|---|
| 1044 | + $ymids[$k] = $ymids[$k - 1]; |
|---|
| 1045 | + } |
|---|
| 1046 | + } |
|---|
| 1047 | + } |
|---|
| 1048 | + } |
|---|
| 1049 | + |
|---|
| 1050 | + $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); |
|---|
| 1051 | + $ymid = $ymids[$this->lcs]; |
|---|
| 1052 | + for ($n = 0; $n < $nchunks - 1; $n++) { |
|---|
| 1053 | + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); |
|---|
| 1054 | + $y1 = $ymid[$n] + 1; |
|---|
| 1055 | + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); |
|---|
| 1056 | + } |
|---|
| 1057 | + $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); |
|---|
| 1058 | + |
|---|
| 1059 | + return array($this->lcs, $seps); |
|---|
| 1060 | + } |
|---|
| 1061 | + |
|---|
| 1062 | + function _lcsPos($ypos) |
|---|
| 1063 | + { |
|---|
| 1064 | + $end = $this->lcs; |
|---|
| 1065 | + if ($end == 0 || $ypos > $this->seq[$end]) { |
|---|
| 1066 | + $this->seq[++$this->lcs] = $ypos; |
|---|
| 1067 | + $this->in_seq[$ypos] = 1; |
|---|
| 1068 | + return $this->lcs; |
|---|
| 1069 | + } |
|---|
| 1070 | + |
|---|
| 1071 | + $beg = 1; |
|---|
| 1072 | + while ($beg < $end) { |
|---|
| 1073 | + $mid = (int)(($beg + $end) / 2); |
|---|
| 1074 | + if ($ypos > $this->seq[$mid]) { |
|---|
| 1075 | + $beg = $mid + 1; |
|---|
| 1076 | + } else { |
|---|
| 1077 | + $end = $mid; |
|---|
| 1078 | + } |
|---|
| 1079 | + } |
|---|
| 1080 | + |
|---|
| 1081 | + assert($ypos != $this->seq[$end]); |
|---|
| 1082 | + |
|---|
| 1083 | + $this->in_seq[$this->seq[$end]] = false; |
|---|
| 1084 | + $this->seq[$end] = $ypos; |
|---|
| 1085 | + $this->in_seq[$ypos] = 1; |
|---|
| 1086 | + return $end; |
|---|
| 1087 | + } |
|---|
| 1088 | + |
|---|
| 1089 | + /** |
|---|
| 1090 | + * Finds LCS of two sequences. |
|---|
| 1091 | + * |
|---|
| 1092 | + * The results are recorded in the vectors $this->{x,y}changed[], by |
|---|
| 1093 | + * storing a 1 in the element for each line that is an insertion or |
|---|
| 1094 | + * deletion (ie. is not in the LCS). |
|---|
| 1095 | + * |
|---|
| 1096 | + * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. |
|---|
| 1097 | + * |
|---|
| 1098 | + * Note that XLIM, YLIM are exclusive bounds. All line numbers are |
|---|
| 1099 | + * origin-0 and discarded lines are not counted. |
|---|
| 1100 | + */ |
|---|
| 1101 | + function _compareseq ($xoff, $xlim, $yoff, $ylim) |
|---|
| 1102 | + { |
|---|
| 1103 | + /* Slide down the bottom initial diagonal. */ |
|---|
| 1104 | + while ($xoff < $xlim && $yoff < $ylim |
|---|
| 1105 | + && $this->xv[$xoff] == $this->yv[$yoff]) { |
|---|
| 1106 | + ++$xoff; |
|---|
| 1107 | + ++$yoff; |
|---|
| 1108 | + } |
|---|
| 1109 | + |
|---|
| 1110 | + /* Slide up the top initial diagonal. */ |
|---|
| 1111 | + while ($xlim > $xoff && $ylim > $yoff |
|---|
| 1112 | + && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { |
|---|
| 1113 | + --$xlim; |
|---|
| 1114 | + --$ylim; |
|---|
| 1115 | + } |
|---|
| 1116 | + |
|---|
| 1117 | + if ($xoff == $xlim || $yoff == $ylim) { |
|---|
| 1118 | + $lcs = 0; |
|---|
| 1119 | + } else { |
|---|
| 1120 | + /* This is ad hoc but seems to work well. $nchunks = |
|---|
| 1121 | + * sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks = |
|---|
| 1122 | + * max(2,min(8,(int)$nchunks)); */ |
|---|
| 1123 | + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; |
|---|
| 1124 | + list($lcs, $seps) |
|---|
| 1125 | + = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); |
|---|
| 1126 | + } |
|---|
| 1127 | + |
|---|
| 1128 | + if ($lcs == 0) { |
|---|
| 1129 | + /* X and Y sequences have no common subsequence: mark all |
|---|
| 1130 | + * changed. */ |
|---|
| 1131 | + while ($yoff < $ylim) { |
|---|
| 1132 | + $this->ychanged[$this->yind[$yoff++]] = 1; |
|---|
| 1133 | + } |
|---|
| 1134 | + while ($xoff < $xlim) { |
|---|
| 1135 | + $this->xchanged[$this->xind[$xoff++]] = 1; |
|---|
| 1136 | + } |
|---|
| 1137 | + } else { |
|---|
| 1138 | + /* Use the partitions to split this problem into subproblems. */ |
|---|
| 1139 | + reset($seps); |
|---|
| 1140 | + $pt1 = $seps[0]; |
|---|
| 1141 | + while ($pt2 = next($seps)) { |
|---|
| 1142 | + $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); |
|---|
| 1143 | + $pt1 = $pt2; |
|---|
| 1144 | + } |
|---|
| 1145 | + } |
|---|
| 1146 | + } |
|---|
| 1147 | + |
|---|
| 1148 | + /** |
|---|
| 1149 | + * Adjusts inserts/deletes of identical lines to join changes as much as |
|---|
| 1150 | + * possible. |
|---|
| 1151 | + * |
|---|
| 1152 | + * We do something when a run of changed lines include a line at one end |
|---|
| 1153 | + * and has an excluded, identical line at the other. We are free to |
|---|
| 1154 | + * choose which identical line is included. `compareseq' usually chooses |
|---|
| 1155 | + * the one at the beginning, but usually it is cleaner to consider the |
|---|
| 1156 | + * following identical line to be the "change". |
|---|
| 1157 | + * |
|---|
| 1158 | + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). |
|---|
| 1159 | + */ |
|---|
| 1160 | + function _shiftBoundaries($lines, &$changed, $other_changed) |
|---|
| 1161 | + { |
|---|
| 1162 | + $i = 0; |
|---|
| 1163 | + $j = 0; |
|---|
| 1164 | + |
|---|
| 1165 | + assert('count($lines) == count($changed)'); |
|---|
| 1166 | + $len = count($lines); |
|---|
| 1167 | + $other_len = count($other_changed); |
|---|
| 1168 | + |
|---|
| 1169 | + while (1) { |
|---|
| 1170 | + /* Scan forward to find the beginning of another run of |
|---|
| 1171 | + * changes. Also keep track of the corresponding point in the |
|---|
| 1172 | + * other file. |
|---|
| 1173 | + * |
|---|
| 1174 | + * Throughout this code, $i and $j are adjusted together so that |
|---|
| 1175 | + * the first $i elements of $changed and the first $j elements of |
|---|
| 1176 | + * $other_changed both contain the same number of zeros (unchanged |
|---|
| 1177 | + * lines). |
|---|
| 1178 | + * |
|---|
| 1179 | + * Furthermore, $j is always kept so that $j == $other_len or |
|---|
| 1180 | + * $other_changed[$j] == false. */ |
|---|
| 1181 | + while ($j < $other_len && $other_changed[$j]) { |
|---|
| 1182 | + $j++; |
|---|
| 1183 | + } |
|---|
| 1184 | + |
|---|
| 1185 | + while ($i < $len && ! $changed[$i]) { |
|---|
| 1186 | + assert('$j < $other_len && ! $other_changed[$j]'); |
|---|
| 1187 | + $i++; $j++; |
|---|
| 1188 | + while ($j < $other_len && $other_changed[$j]) { |
|---|
| 1189 | + $j++; |
|---|
| 1190 | + } |
|---|
| 1191 | + } |
|---|
| 1192 | + |
|---|
| 1193 | + if ($i == $len) { |
|---|
| 1194 | + break; |
|---|
| 1195 | + } |
|---|
| 1196 | + |
|---|
| 1197 | + $start = $i; |
|---|
| 1198 | + |
|---|
| 1199 | + /* Find the end of this run of changes. */ |
|---|
| 1200 | + while (++$i < $len && $changed[$i]) { |
|---|
| 1201 | + continue; |
|---|
| 1202 | + } |
|---|
| 1203 | + |
|---|
| 1204 | + do { |
|---|
| 1205 | + /* Record the length of this run of changes, so that we can |
|---|
| 1206 | + * later determine whether the run has grown. */ |
|---|
| 1207 | + $runlength = $i - $start; |
|---|
| 1208 | + |
|---|
| 1209 | + /* Move the changed region back, so long as the previous |
|---|
| 1210 | + * unchanged line matches the last changed one. This merges |
|---|
| 1211 | + * with previous changed regions. */ |
|---|
| 1212 | + while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { |
|---|
| 1213 | + $changed[--$start] = 1; |
|---|
| 1214 | + $changed[--$i] = false; |
|---|
| 1215 | + while ($start > 0 && $changed[$start - 1]) { |
|---|
| 1216 | + $start--; |
|---|
| 1217 | + } |
|---|
| 1218 | + assert('$j > 0'); |
|---|
| 1219 | + while ($other_changed[--$j]) { |
|---|
| 1220 | + continue; |
|---|
| 1221 | + } |
|---|
| 1222 | + assert('$j >= 0 && !$other_changed[$j]'); |
|---|
| 1223 | + } |
|---|
| 1224 | + |
|---|
| 1225 | + /* Set CORRESPONDING to the end of the changed run, at the |
|---|
| 1226 | + * last point where it corresponds to a changed run in the |
|---|
| 1227 | + * other file. CORRESPONDING == LEN means no such point has |
|---|
| 1228 | + * been found. */ |
|---|
| 1229 | + $corresponding = $j < $other_len ? $i : $len; |
|---|
| 1230 | + |
|---|
| 1231 | + /* Move the changed region forward, so long as the first |
|---|
| 1232 | + * changed line matches the following unchanged one. This |
|---|
| 1233 | + * merges with following changed regions. Do this second, so |
|---|
| 1234 | + * that if there are no merges, the changed region is moved |
|---|
| 1235 | + * forward as far as possible. */ |
|---|
| 1236 | + while ($i < $len && $lines[$start] == $lines[$i]) { |
|---|
| 1237 | + $changed[$start++] = false; |
|---|
| 1238 | + $changed[$i++] = 1; |
|---|
| 1239 | + while ($i < $len && $changed[$i]) { |
|---|
| 1240 | + $i++; |
|---|
| 1241 | + } |
|---|
| 1242 | + |
|---|
| 1243 | + assert('$j < $other_len && ! $other_changed[$j]'); |
|---|
| 1244 | + $j++; |
|---|
| 1245 | + if ($j < $other_len && $other_changed[$j]) { |
|---|
| 1246 | + $corresponding = $i; |
|---|
| 1247 | + while ($j < $other_len && $other_changed[$j]) { |
|---|
| 1248 | + $j++; |
|---|
| 1249 | + } |
|---|
| 1250 | + } |
|---|
| 1251 | + } |
|---|
| 1252 | + } while ($runlength != $i - $start); |
|---|
| 1253 | + |
|---|
| 1254 | + /* If possible, move the fully-merged run of changes back to a |
|---|
| 1255 | + * corresponding run in the other file. */ |
|---|
| 1256 | + while ($corresponding < $i) { |
|---|
| 1257 | + $changed[--$start] = 1; |
|---|
| 1258 | + $changed[--$i] = 0; |
|---|
| 1259 | + assert('$j > 0'); |
|---|
| 1260 | + while ($other_changed[--$j]) { |
|---|
| 1261 | + continue; |
|---|
| 1262 | + } |
|---|
| 1263 | + assert('$j >= 0 && !$other_changed[$j]'); |
|---|
| 1264 | + } |
|---|
| 1265 | + } |
|---|
| 1266 | + } |
|---|
| 1267 | + |
|---|
| 1268 | +} |
|---|
| 1269 | Index: wp-includes/Text/Diff/Engine/string.php |
|---|
| 1270 | =================================================================== |
|---|
| 1271 | --- wp-includes/Text/Diff/Engine/string.php (revision 0) |
|---|
| 1272 | +++ wp-includes/Text/Diff/Engine/string.php (revision 0) |
|---|
| 1273 | @@ -0,0 +1,234 @@ |
|---|
| 1274 | +<?php |
|---|
| 1275 | +/** |
|---|
| 1276 | + * Parses unified or context diffs output from eg. the diff utility. |
|---|
| 1277 | + * |
|---|
| 1278 | + * Example: |
|---|
| 1279 | + * <code> |
|---|
| 1280 | + * $patch = file_get_contents('example.patch'); |
|---|
| 1281 | + * $diff = new Text_Diff('string', array($patch)); |
|---|
| 1282 | + * $renderer = new Text_Diff_Renderer_inline(); |
|---|
| 1283 | + * echo $renderer->render($diff); |
|---|
| 1284 | + * </code> |
|---|
| 1285 | + * |
|---|
| 1286 | + * $Horde: framework/Text_Diff/Diff/Engine/string.php,v 1.7 2008/01/04 10:07:50 jan Exp $ |
|---|
| 1287 | + * |
|---|
| 1288 | + * Copyright 2005 Örjan Persson <o@42mm.org> |
|---|
| 1289 | + * Copyright 2005-2008 The Horde Project (http://www.horde.org/) |
|---|
| 1290 | + * |
|---|
| 1291 | + * See the enclosed file COPYING for license information (LGPL). If you did |
|---|
| 1292 | + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. |
|---|
| 1293 | + * |
|---|
| 1294 | + * @author Örjan Persson <o@42mm.org> |
|---|
| 1295 | + * @package Text_Diff |
|---|
| 1296 | + * @since 0.2.0 |
|---|
| 1297 | + */ |
|---|
| 1298 | +class Text_Diff_Engine_string { |
|---|
| 1299 | + |
|---|
| 1300 | + /** |
|---|
| 1301 | + * Parses a unified or context diff. |
|---|
| 1302 | + * |
|---|
| 1303 | + * First param contains the whole diff and the second can be used to force |
|---|
| 1304 | + * a specific diff type. If the second parameter is 'autodetect', the |
|---|
| 1305 | + * diff will be examined to find out which type of diff this is. |
|---|
| 1306 | + * |
|---|
| 1307 | + * @param string $diff The diff content. |
|---|
| 1308 | + * @param string $mode The diff mode of the content in $diff. One of |
|---|
| 1309 | + * 'context', 'unified', or 'autodetect'. |
|---|
| 1310 | + * |
|---|
| 1311 | + * @return array List of all diff operations. |
|---|
| 1312 | + */ |
|---|
| 1313 | + function diff($diff, $mode = 'autodetect') |
|---|
| 1314 | + { |
|---|
| 1315 | + if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { |
|---|
| 1316 | + return PEAR::raiseError('Type of diff is unsupported'); |
|---|
| 1317 | + } |
|---|
| 1318 | + |
|---|
| 1319 | + if ($mode == 'autodetect') { |
|---|
| 1320 | + $context = strpos($diff, '***'); |
|---|
| 1321 | + $unified = strpos($diff, '---'); |
|---|
| 1322 | + if ($context === $unified) { |
|---|
| 1323 | + return PEAR::raiseError('Type of diff could not be detected'); |
|---|
| 1324 | + } elseif ($context === false || $context === false) { |
|---|
| 1325 | + $mode = $context !== false ? 'context' : 'unified'; |
|---|
| 1326 | + } else { |
|---|
| 1327 | + $mode = $context < $unified ? 'context' : 'unified'; |
|---|
| 1328 | + } |
|---|
| 1329 | + } |
|---|
| 1330 | + |
|---|
| 1331 | + // split by new line and remove the diff header |
|---|
| 1332 | + $diff = explode("\n", $diff); |
|---|
| 1333 | + array_shift($diff); |
|---|
| 1334 | + array_shift($diff); |
|---|
| 1335 | + |
|---|
| 1336 | + if ($mode == 'context') { |
|---|
| 1337 | + return $this->parseContextDiff($diff); |
|---|
| 1338 | + } else { |
|---|
| 1339 | + return $this->parseUnifiedDiff($diff); |
|---|
| 1340 | + } |
|---|
| 1341 | + } |
|---|
| 1342 | + |
|---|
| 1343 | + /** |
|---|
| 1344 | + * Parses an array containing the unified diff. |
|---|
| 1345 | + * |
|---|
| 1346 | + * @param array $diff Array of lines. |
|---|
| 1347 | + * |
|---|
| 1348 | + * @return array List of all diff operations. |
|---|
| 1349 | + */ |
|---|
| 1350 | + function parseUnifiedDiff($diff) |
|---|
| 1351 | + { |
|---|
| 1352 | + $edits = array(); |
|---|
| 1353 | + $end = count($diff) - 1; |
|---|
| 1354 | + for ($i = 0; $i < $end;) { |
|---|
| 1355 | + $diff1 = array(); |
|---|
| 1356 | + switch (substr($diff[$i], 0, 1)) { |
|---|
| 1357 | + case ' ': |
|---|
| 1358 | + do { |
|---|
| 1359 | + $diff1[] = substr($diff[$i], 1); |
|---|
| 1360 | + } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); |
|---|
| 1361 | + $edits[] = &new Text_Diff_Op_copy($diff1); |
|---|
| 1362 | + break; |
|---|
| 1363 | + |
|---|
| 1364 | + case '+': |
|---|
| 1365 | + // get all new lines |
|---|
| 1366 | + do { |
|---|
| 1367 | + $diff1[] = substr($diff[$i], 1); |
|---|
| 1368 | + } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); |
|---|
| 1369 | + $edits[] = &new Text_Diff_Op_add($diff1); |
|---|
| 1370 | + break; |
|---|
| 1371 | + |
|---|
| 1372 | + case '-': |
|---|
| 1373 | + // get changed or removed lines |
|---|
| 1374 | + $diff2 = array(); |
|---|
| 1375 | + do { |
|---|
| 1376 | + $diff1[] = substr($diff[$i], 1); |
|---|
| 1377 | + } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); |
|---|
| 1378 | + |
|---|
| 1379 | + while ($i < $end && substr($diff[$i], 0, 1) == '+') { |
|---|
| 1380 | + $diff2[] = substr($diff[$i++], 1); |
|---|
| 1381 | + } |
|---|
| 1382 | + if (count($diff2) == 0) { |
|---|
| 1383 | + $edits[] = &new Text_Diff_Op_delete($diff1); |
|---|
| 1384 | + } else { |
|---|
| 1385 | + $edits[] = &new Text_Diff_Op_change($diff1, $diff2); |
|---|
| 1386 | + } |
|---|
| 1387 | + break; |
|---|
| 1388 | + |
|---|
| 1389 | + default: |
|---|
| 1390 | + $i++; |
|---|
| 1391 | + break; |
|---|
| 1392 | + } |
|---|
| 1393 | + } |
|---|
| 1394 | + |
|---|
| 1395 | + return $edits; |
|---|
| 1396 | + } |
|---|
| 1397 | + |
|---|
| 1398 | + /** |
|---|
| 1399 | + * Parses an array containing the context diff. |
|---|
| 1400 | + * |
|---|
| 1401 | + * @param array $diff Array of lines. |
|---|
| 1402 | + * |
|---|
| 1403 | + * @return array List of all diff operations. |
|---|
| 1404 | + */ |
|---|
| 1405 | + function parseContextDiff(&$diff) |
|---|
| 1406 | + { |
|---|
| 1407 | + $edits = array(); |
|---|
| 1408 | + $i = $max_i = $j = $max_j = 0; |
|---|
| 1409 | + $end = count($diff) - 1; |
|---|
| 1410 | + while ($i < $end && $j < $end) { |
|---|
| 1411 | + while ($i >= $max_i && $j >= $max_j) { |
|---|
| 1412 | + // Find the boundaries of the diff output of the two files |
|---|
| 1413 | + for ($i = $j; |
|---|
| 1414 | + $i < $end && substr($diff[$i], 0, 3) == '***'; |
|---|
| 1415 | + $i++); |
|---|
| 1416 | + for ($max_i = $i; |
|---|
| 1417 | + $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; |
|---|
| 1418 | + $max_i++); |
|---|
| 1419 | + for ($j = $max_i; |
|---|
| 1420 | + $j < $end && substr($diff[$j], 0, 3) == '---'; |
|---|
| 1421 | + $j++); |
|---|
| 1422 | + for ($max_j = $j; |
|---|
| 1423 | + $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; |
|---|
| 1424 | + $max_j++); |
|---|
| 1425 | + } |
|---|
| 1426 | + |
|---|
| 1427 | + // find what hasn't been changed |
|---|
| 1428 | + $array = array(); |
|---|
| 1429 | + while ($i < $max_i && |
|---|
| 1430 | + $j < $max_j && |
|---|
| 1431 | + strcmp($diff[$i], $diff[$j]) == 0) { |
|---|
| 1432 | + $array[] = substr($diff[$i], 2); |
|---|
| 1433 | + $i++; |
|---|
| 1434 | + $j++; |
|---|
| 1435 | + } |
|---|
| 1436 | + |
|---|
| 1437 | + while ($i < $max_i && ($max_j-$j) <= 1) { |
|---|
| 1438 | + if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { |
|---|
| 1439 | + break; |
|---|
| 1440 | + } |
|---|
| 1441 | + $array[] = substr($diff[$i++], 2); |
|---|
| 1442 | + } |
|---|
| 1443 | + |
|---|
| 1444 | + while ($j < $max_j && ($max_i-$i) <= 1) { |
|---|
| 1445 | + if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { |
|---|
| 1446 | + break; |
|---|
| 1447 | + } |
|---|
| 1448 | + $array[] = substr($diff[$j++], 2); |
|---|
| 1449 | + } |
|---|
| 1450 | + if (count($array) > 0) { |
|---|
| 1451 | + $edits[] = &new Text_Diff_Op_copy($array); |
|---|
| 1452 | + } |
|---|
| 1453 | + |
|---|
| 1454 | + if ($i < $max_i) { |
|---|
| 1455 | + $diff1 = array(); |
|---|
| 1456 | + switch (substr($diff[$i], 0, 1)) { |
|---|
| 1457 | + case '!': |
|---|
| 1458 | + $diff2 = array(); |
|---|
| 1459 | + do { |
|---|
| 1460 | + $diff1[] = substr($diff[$i], 2); |
|---|
| 1461 | + if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { |
|---|
| 1462 | + $diff2[] = substr($diff[$j++], 2); |
|---|
| 1463 | + } |
|---|
| 1464 | + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); |
|---|
| 1465 | + $edits[] = &new Text_Diff_Op_change($diff1, $diff2); |
|---|
| 1466 | + break; |
|---|
| 1467 | + |
|---|
| 1468 | + case '+': |
|---|
| 1469 | + do { |
|---|
| 1470 | + $diff1[] = substr($diff[$i], 2); |
|---|
| 1471 | + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); |
|---|
| 1472 | + $edits[] = &new Text_Diff_Op_add($diff1); |
|---|
| 1473 | + break; |
|---|
| 1474 | + |
|---|
| 1475 | + case '-': |
|---|
| 1476 | + do { |
|---|
| 1477 | + $diff1[] = substr($diff[$i], 2); |
|---|
| 1478 | + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); |
|---|
| 1479 | + $edits[] = &new Text_Diff_Op_delete($diff1); |
|---|
| 1480 | + break; |
|---|
| 1481 | + } |
|---|
| 1482 | + } |
|---|
| 1483 | + |
|---|
| 1484 | + if ($j < $max_j) { |
|---|
| 1485 | + $diff2 = array(); |
|---|
| 1486 | + switch (substr($diff[$j], 0, 1)) { |
|---|
| 1487 | + case '+': |
|---|
| 1488 | + do { |
|---|
| 1489 | + $diff2[] = substr($diff[$j++], 2); |
|---|
| 1490 | + } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); |
|---|
| 1491 | + $edits[] = &new Text_Diff_Op_add($diff2); |
|---|
| 1492 | + break; |
|---|
| 1493 | + |
|---|
| 1494 | + case '-': |
|---|
| 1495 | + do { |
|---|
| 1496 | + $diff2[] = substr($diff[$j++], 2); |
|---|
| 1497 | + } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); |
|---|
| 1498 | + $edits[] = &new Text_Diff_Op_delete($diff2); |
|---|
| 1499 | + break; |
|---|
| 1500 | + } |
|---|
| 1501 | + } |
|---|
| 1502 | + } |
|---|
| 1503 | + |
|---|
| 1504 | + return $edits; |
|---|
| 1505 | + } |
|---|
| 1506 | + |
|---|
| 1507 | +} |
|---|
| 1508 | Index: wp-includes/Text/Diff/Engine/shell.php |
|---|
| 1509 | =================================================================== |
|---|
| 1510 | --- wp-includes/Text/Diff/Engine/shell.php (revision 0) |
|---|
| 1511 | +++ wp-includes/Text/Diff/Engine/shell.php (revision 0) |
|---|
| 1512 | @@ -0,0 +1,164 @@ |
|---|
| 1513 | +<?php |
|---|
| 1514 | +/** |
|---|
| 1515 | + * Class used internally by Diff to actually compute the diffs. |
|---|
| 1516 | + * |
|---|
| 1517 | + * This class uses the Unix `diff` program via shell_exec to compute the |
|---|
| 1518 | + * differences between the two input arrays. |
|---|
| 1519 | + * |
|---|
| 1520 | + * $Horde: framework/Text_Diff/Diff/Engine/shell.php,v 1.8 2008/01/04 10:07:50 jan Exp $ |
|---|
| 1521 | + * |
|---|
| 1522 | + * Copyright 2007-2008 The Horde Project (http://www.horde.org/) |
|---|
| 1523 | + * |
|---|
| 1524 | + * See the enclosed file COPYING for license information (LGPL). If you did |
|---|
| 1525 | + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. |
|---|
| 1526 | + * |
|---|
| 1527 | + * @author Milian Wolff <mail@milianw.de> |
|---|
| 1528 | + * @package Text_Diff |
|---|
| 1529 | + * @since 0.3.0 |
|---|
| 1530 | + */ |
|---|
| 1531 | +class Text_Diff_Engine_shell { |
|---|
| 1532 | + |
|---|
| 1533 | + /** |
|---|
| 1534 | + * Path to the diff executable |
|---|
| 1535 | + * |
|---|
| 1536 | + * @var string |
|---|
| 1537 | + */ |
|---|
| 1538 | + var $_diffCommand = 'diff'; |
|---|
| 1539 | + |
|---|
| 1540 | + /** |
|---|
| 1541 | + * Returns the array of differences. |
|---|
| 1542 | + * |
|---|
| 1543 | + * @param array $from_lines lines of text from old file |
|---|
| 1544 | + * @param array $to_lines lines of text from new file |
|---|
| 1545 | + * |
|---|
| 1546 | + * @return array all changes made (array with Text_Diff_Op_* objects) |
|---|
| 1547 | + */ |
|---|
| 1548 | + function diff($from_lines, $to_lines) |
|---|
| 1549 | + { |
|---|
| 1550 | + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); |
|---|
| 1551 | + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); |
|---|
| 1552 | + |
|---|
| 1553 | + $temp_dir = Text_Diff::_getTempDir(); |
|---|
| 1554 | + |
|---|
| 1555 | + // Execute gnu diff or similar to get a standard diff file. |
|---|
| 1556 | + $from_file = tempnam($temp_dir, 'Text_Diff'); |
|---|
| 1557 | + $to_file = tempnam($temp_dir, 'Text_Diff'); |
|---|
| 1558 | + $fp = fopen($from_file, 'w'); |
|---|
| 1559 | + fwrite($fp, implode("\n", $from_lines)); |
|---|
| 1560 | + fclose($fp); |
|---|
| 1561 | + $fp = fopen($to_file, 'w'); |
|---|
| 1562 | + fwrite($fp, implode("\n", $to_lines)); |
|---|
| 1563 | + fclose($fp); |
|---|
| 1564 | + $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); |
|---|
| 1565 | + unlink($from_file); |
|---|
| 1566 | + unlink($to_file); |
|---|
| 1567 | + |
|---|
| 1568 | + if (is_null($diff)) { |
|---|
| 1569 | + // No changes were made |
|---|
| 1570 | + return array(new Text_Diff_Op_copy($from_lines)); |
|---|
| 1571 | + } |
|---|
| 1572 | + |
|---|
| 1573 | + $from_line_no = 1; |
|---|
| 1574 | + $to_line_no = 1; |
|---|
| 1575 | + $edits = array(); |
|---|
| 1576 | + |
|---|
| 1577 | + // Get changed lines by parsing something like: |
|---|
| 1578 | + // 0a1,2 |
|---|
| 1579 | + // 1,2c4,6 |
|---|
| 1580 | + // 1,5d6 |
|---|
| 1581 | + preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, |
|---|
| 1582 | + $matches, PREG_SET_ORDER); |
|---|
| 1583 | + |
|---|
| 1584 | + foreach ($matches as $match) { |
|---|
| 1585 | + if (!isset($match[5])) { |
|---|
| 1586 | + // This paren is not set every time (see regex). |
|---|
| 1587 | + $match[5] = false; |
|---|
| 1588 | + } |
|---|
| 1589 | + |
|---|
| 1590 | + if ($match[3] == 'a') { |
|---|
| 1591 | + $from_line_no--; |
|---|
| 1592 | + } |
|---|
| 1593 | + |
|---|
| 1594 | + if ($match[3] == 'd') { |
|---|
| 1595 | + $to_line_no--; |
|---|
| 1596 | + } |
|---|
| 1597 | + |
|---|
| 1598 | + if ($from_line_no < $match[1] || $to_line_no < $match[4]) { |
|---|
| 1599 | + // copied lines |
|---|
| 1600 | + assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); |
|---|
| 1601 | + array_push($edits, |
|---|
| 1602 | + new Text_Diff_Op_copy( |
|---|
| 1603 | + $this->_getLines($from_lines, $from_line_no, $match[1] - 1), |
|---|
| 1604 | + $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); |
|---|
| 1605 | + } |
|---|
| 1606 | + |
|---|
| 1607 | + switch ($match[3]) { |
|---|
| 1608 | + case 'd': |
|---|
| 1609 | + // deleted lines |
|---|
| 1610 | + array_push($edits, |
|---|
| 1611 | + new Text_Diff_Op_delete( |
|---|
| 1612 | + $this->_getLines($from_lines, $from_line_no, $match[2]))); |
|---|
| 1613 | + $to_line_no++; |
|---|
| 1614 | + break; |
|---|
| 1615 | + |
|---|
| 1616 | + case 'c': |
|---|
| 1617 | + // changed lines |
|---|
| 1618 | + array_push($edits, |
|---|
| 1619 | + new Text_Diff_Op_change( |
|---|
| 1620 | + $this->_getLines($from_lines, $from_line_no, $match[2]), |
|---|
| 1621 | + $this->_getLines($to_lines, $to_line_no, $match[5]))); |
|---|
| 1622 | + break; |
|---|
| 1623 | + |
|---|
| 1624 | + case 'a': |
|---|
| 1625 | + // added lines |
|---|
| 1626 | + array_push($edits, |
|---|
| 1627 | + new Text_Diff_Op_add( |
|---|
| 1628 | + $this->_getLines($to_lines, $to_line_no, $match[5]))); |
|---|
| 1629 | + $from_line_no++; |
|---|
| 1630 | + break; |
|---|
| 1631 | + } |
|---|
| 1632 | + } |
|---|
| 1633 | + |
|---|
| 1634 | + if (!empty($from_lines)) { |
|---|
| 1635 | + // Some lines might still be pending. Add them as copied |
|---|
| 1636 | + array_push($edits, |
|---|
| 1637 | + new Text_Diff_Op_copy( |
|---|
| 1638 | + $this->_getLines($from_lines, $from_line_no, |
|---|
| 1639 | + $from_line_no + count($from_lines) - 1), |
|---|
| 1640 | + $this->_getLines($to_lines, $to_line_no, |
|---|
| 1641 | + $to_line_no + count($to_lines) - 1))); |
|---|
| 1642 | + } |
|---|
| 1643 | + |
|---|
| 1644 | + return $edits; |
|---|
| 1645 | + } |
|---|
| 1646 | + |
|---|
| 1647 | + /** |
|---|
| 1648 | + * Get lines from either the old or new text |
|---|
| 1649 | + * |
|---|
| 1650 | + * @access private |
|---|
| 1651 | + * |
|---|
| 1652 | + * @param array &$text_lines Either $from_lines or $to_lines |
|---|
| 1653 | + * @param int &$line_no Current line number |
|---|
| 1654 | + * @param int $end Optional end line, when we want to chop more |
|---|
| 1655 | + * than one line. |
|---|
| 1656 | + * |
|---|
| 1657 | + * @return array The chopped lines |
|---|
| 1658 | + */ |
|---|
| 1659 | + function _getLines(&$text_lines, &$line_no, $end = false) |
|---|
| 1660 | + { |
|---|
| 1661 | + if (!empty($end)) { |
|---|
| 1662 | + $lines = array(); |
|---|
| 1663 | + // We can shift even more |
|---|
| 1664 | + while ($line_no <= $end) { |
|---|
| 1665 | + array_push($lines, array_shift($text_lines)); |
|---|
| 1666 | + $line_no++; |
|---|
| 1667 | + } |
|---|
| 1668 | + } else { |
|---|
| 1669 | + $lines = array(array_shift($text_lines)); |
|---|
| 1670 | + $line_no++; |
|---|
| 1671 | + } |
|---|
| 1672 | + |
|---|
| 1673 | + return $lines; |
|---|
| 1674 | + } |
|---|
| 1675 | + |
|---|
| 1676 | +} |
|---|
| 1677 | Index: wp-includes/Text/Diff/Renderer/inline.php |
|---|
| 1678 | =================================================================== |
|---|
| 1679 | --- wp-includes/Text/Diff/Renderer/inline.php (revision 0) |
|---|
| 1680 | +++ wp-includes/Text/Diff/Renderer/inline.php (revision 0) |
|---|
| 1681 | @@ -0,0 +1,170 @@ |
|---|
| 1682 | +<?php |
|---|
| 1683 | +/** |
|---|
| 1684 | + * "Inline" diff renderer. |
|---|
| 1685 | + * |
|---|
| 1686 | + * $Horde: framework/Text_Diff/Diff/Renderer/inline.php,v 1.21 2008/01/04 10:07:51 jan Exp $ |
|---|
| 1687 | + * |
|---|
| 1688 | + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) |
|---|
| 1689 | + * |
|---|
| 1690 | + * See the enclosed file COPYING for license information (LGPL). If you did |
|---|
| 1691 | + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. |
|---|
| 1692 | + * |
|---|
| 1693 | + * @author Ciprian Popovici |
|---|
| 1694 | + * @package Text_Diff |
|---|
| 1695 | + */ |
|---|
| 1696 | + |
|---|
| 1697 | +/** Text_Diff_Renderer */ |
|---|
| 1698 | +require_once 'Text/Diff/Renderer.php'; |
|---|
| 1699 | + |
|---|
| 1700 | +/** |
|---|
| 1701 | + * "Inline" diff renderer. |
|---|
| 1702 | + * |
|---|
| 1703 | + * This class renders diffs in the Wiki-style "inline" format. |
|---|
| 1704 | + * |
|---|
| 1705 | + * @author Ciprian Popovici |
|---|
| 1706 | + * @package Text_Diff |
|---|
| 1707 | + */ |
|---|
| 1708 | +class Text_Diff_Renderer_inline extends Text_Diff_Renderer { |
|---|
| 1709 | + |
|---|
| 1710 | + /** |
|---|
| 1711 | + * Number of leading context "lines" to preserve. |
|---|
| 1712 | + */ |
|---|
| 1713 | + var $_leading_context_lines = 10000; |
|---|
| 1714 | + |
|---|
| 1715 | + /** |
|---|
| 1716 | + * Number of trailing context "lines" to preserve. |
|---|
| 1717 | + */ |
|---|
| 1718 | + var $_trailing_context_lines = 10000; |
|---|
| 1719 | + |
|---|
| 1720 | + /** |
|---|
| 1721 | + * Prefix for inserted text. |
|---|
| 1722 | + */ |
|---|
| 1723 | + var $_ins_prefix = '<ins>'; |
|---|
| 1724 | + |
|---|
| 1725 | + /** |
|---|
| 1726 | + * Suffix for inserted text. |
|---|
| 1727 | + */ |
|---|
| 1728 | + var $_ins_suffix = '</ins>'; |
|---|
| 1729 | + |
|---|
| 1730 | + /** |
|---|
| 1731 | + * Prefix for deleted text. |
|---|
| 1732 | + */ |
|---|
| 1733 | + var $_del_prefix = '<del>'; |
|---|
| 1734 | + |
|---|
| 1735 | + /** |
|---|
| 1736 | + * Suffix for deleted text. |
|---|
| 1737 | + */ |
|---|
| 1738 | + var $_del_suffix = '</del>'; |
|---|
| 1739 | + |
|---|
| 1740 | + /** |
|---|
| 1741 | + * Header for each change block. |
|---|
| 1742 | + */ |
|---|
| 1743 | + var $_block_header = ''; |
|---|
| 1744 | + |
|---|
| 1745 | + /** |
|---|
| 1746 | + * What are we currently splitting on? Used to recurse to show word-level |
|---|
| 1747 | + * changes. |
|---|
| 1748 | + */ |
|---|
| 1749 | + var $_split_level = 'lines'; |
|---|
| 1750 | + |
|---|
| 1751 | + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
|---|
| 1752 | + { |
|---|
| 1753 | + return $this->_block_header; |
|---|
| 1754 | + } |
|---|
| 1755 | + |
|---|
| 1756 | + function _startBlock($header) |
|---|
| 1757 | + { |
|---|
| 1758 | + return $header; |
|---|
| 1759 | + } |
|---|
| 1760 | + |
|---|
| 1761 | + function _lines($lines, $prefix = ' ', $encode = true) |
|---|
| 1762 | + { |
|---|
| 1763 | + if ($encode) { |
|---|
| 1764 | + array_walk($lines, array(&$this, '_encode')); |
|---|
| 1765 | + } |
|---|
| 1766 | + |
|---|
| 1767 | + if ($this->_split_level == 'words') { |
|---|
| 1768 | + return implode('', $lines); |
|---|
| 1769 | + } else { |
|---|
| 1770 | + return implode("\n", $lines) . "\n"; |
|---|
| 1771 | + } |
|---|
| 1772 | + } |
|---|
| 1773 | + |
|---|
| 1774 | + function _added($lines) |
|---|
| 1775 | + { |
|---|
| 1776 | + array_walk($lines, array(&$this, '_encode')); |
|---|
| 1777 | + $lines[0] = $this->_ins_prefix . $lines[0]; |
|---|
| 1778 | + $lines[count($lines) - 1] .= $this->_ins_suffix; |
|---|
| 1779 | + return $this->_lines($lines, ' ', false); |
|---|
| 1780 | + } |
|---|
| 1781 | + |
|---|
| 1782 | + function _deleted($lines, $words = false) |
|---|
| 1783 | + { |
|---|
| 1784 | + array_walk($lines, array(&$this, '_encode')); |
|---|
| 1785 | + $lines[0] = $this->_del_prefix . $lines[0]; |
|---|
| 1786 | + $lines[count($lines) - 1] .= $this->_del_suffix; |
|---|
| 1787 | + return $this->_lines($lines, ' ', false); |
|---|
| 1788 | + } |
|---|
| 1789 | + |
|---|
| 1790 | + function _changed($orig, $final) |
|---|
| 1791 | + { |
|---|
| 1792 | + /* If we've already split on words, don't try to do so again - just |
|---|
| 1793 | + * display. */ |
|---|
| 1794 | + if ($this->_split_level == 'words') { |
|---|
| 1795 | + $prefix = ''; |
|---|
| 1796 | + while ($orig[0] !== false && $final[0] !== false && |
|---|
| 1797 | + substr($orig[0], 0, 1) == ' ' && |
|---|
| 1798 | + substr($final[0], 0, 1) == ' ') { |
|---|
| 1799 | + $prefix .= substr($orig[0], 0, 1); |
|---|
| 1800 | + $orig[0] = substr($orig[0], 1); |
|---|
| 1801 | + $final[0] = substr($final[0], 1); |
|---|
| 1802 | + } |
|---|
| 1803 | + return $prefix . $this->_deleted($orig) . $this->_added($final); |
|---|
| 1804 | + } |
|---|
| 1805 | + |
|---|
| 1806 | + $text1 = implode("\n", $orig); |
|---|
| 1807 | + $text2 = implode("\n", $final); |
|---|
| 1808 | + |
|---|
| 1809 | + /* Non-printing newline marker. */ |
|---|
| 1810 | + $nl = "\0"; |
|---|
| 1811 | + |
|---|
| 1812 | + /* We want to split on word boundaries, but we need to |
|---|
| 1813 | + * preserve whitespace as well. Therefore we split on words, |
|---|
| 1814 | + * but include all blocks of whitespace in the wordlist. */ |
|---|
| 1815 | + $diff = new Text_Diff($this->_splitOnWords($text1, $nl), |
|---|
| 1816 | + $this->_splitOnWords($text2, $nl)); |
|---|
| 1817 | + |
|---|
| 1818 | + /* Get the diff in inline format. */ |
|---|
| 1819 | + $renderer = new Text_Diff_Renderer_inline(array_merge($this->getParams(), |
|---|
| 1820 | + array('split_level' => 'words'))); |
|---|
| 1821 | + |
|---|
| 1822 | + /* Run the diff and get the output. */ |
|---|
| 1823 | + return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; |
|---|
| 1824 | + } |
|---|
| 1825 | + |
|---|
| 1826 | + function _splitOnWords($string, $newlineEscape = "\n") |
|---|
| 1827 | + { |
|---|
| 1828 | + // Ignore \0; otherwise the while loop will never finish. |
|---|
| 1829 | + $string = str_replace("\0", '', $string); |
|---|
| 1830 | + |
|---|
| 1831 | + $words = array(); |
|---|
| 1832 | + $length = strlen($string); |
|---|
| 1833 | + $pos = 0; |
|---|
| 1834 | + |
|---|
| 1835 | + while ($pos < $length) { |
|---|
| 1836 | + // Eat a word with any preceding whitespace. |
|---|
| 1837 | + $spaces = strspn(substr($string, $pos), " \n"); |
|---|
| 1838 | + $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); |
|---|
| 1839 | + $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); |
|---|
| 1840 | + $pos += $spaces + $nextpos; |
|---|
| 1841 | + } |
|---|
| 1842 | + |
|---|
| 1843 | + return $words; |
|---|
| 1844 | + } |
|---|
| 1845 | + |
|---|
| 1846 | + function _encode(&$string) |
|---|
| 1847 | + { |
|---|
| 1848 | + $string = htmlspecialchars($string); |
|---|
| 1849 | + } |
|---|
| 1850 | + |
|---|
| 1851 | +} |
|---|
| 1852 | Index: wp-includes/Text/Diff/Renderer.php |
|---|
| 1853 | =================================================================== |
|---|
| 1854 | --- wp-includes/Text/Diff/Renderer.php (revision 0) |
|---|
| 1855 | +++ wp-includes/Text/Diff/Renderer.php (revision 0) |
|---|
| 1856 | @@ -0,0 +1,237 @@ |
|---|
| 1857 | +<?php |
|---|
| 1858 | +/** |
|---|
| 1859 | + * A class to render Diffs in different formats. |
|---|
| 1860 | + * |
|---|
| 1861 | + * This class renders the diff in classic diff format. It is intended that |
|---|
| 1862 | + * this class be customized via inheritance, to obtain fancier outputs. |
|---|
| 1863 | + * |
|---|
| 1864 | + * $Horde: framework/Text_Diff/Diff/Renderer.php,v 1.21 2008/01/04 10:07:50 jan Exp $ |
|---|
| 1865 | + * |
|---|
| 1866 | + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) |
|---|
| 1867 | + * |
|---|
| 1868 | + * See the enclosed file COPYING for license information (LGPL). If you did |
|---|
| 1869 | + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. |
|---|
| 1870 | + * |
|---|
| 1871 | + * @package Text_Diff |
|---|
| 1872 | + */ |
|---|
| 1873 | +class Text_Diff_Renderer { |
|---|
| 1874 | + |
|---|
| 1875 | + /** |
|---|
| 1876 | + * Number of leading context "lines" to preserve. |
|---|
| 1877 | + * |
|---|
| 1878 | + * This should be left at zero for this class, but subclasses may want to |
|---|
| 1879 | + * set this to other values. |
|---|
| 1880 | + */ |
|---|
| 1881 | + var $_leading_context_lines = 0; |
|---|
| 1882 | + |
|---|
| 1883 | + /** |
|---|
| 1884 | + * Number of trailing context "lines" to preserve. |
|---|
| 1885 | + * |
|---|
| 1886 | + * This should be left at zero for this class, but subclasses may want to |
|---|
| 1887 | + * set this to other values. |
|---|
| 1888 | + */ |
|---|
| 1889 | + var $_trailing_context_lines = 0; |
|---|
| 1890 | + |
|---|
| 1891 | + /** |
|---|
| 1892 | + * Constructor. |
|---|
| 1893 | + */ |
|---|
| 1894 | + function Text_Diff_Renderer($params = array()) |
|---|
| 1895 | + { |
|---|
| 1896 | + foreach ($params as $param => $value) { |
|---|
| 1897 | + $v = '_' . $param; |
|---|
| 1898 | + if (isset($this->$v)) { |
|---|
| 1899 | + $this->$v = $value; |
|---|
| 1900 | + } |
|---|
| 1901 | + } |
|---|
| 1902 | + } |
|---|
| 1903 | + |
|---|
| 1904 | + /** |
|---|
| 1905 | + * Get any renderer parameters. |
|---|
| 1906 | + * |
|---|
| 1907 | + * @return array All parameters of this renderer object. |
|---|
| 1908 | + */ |
|---|
| 1909 | + function getParams() |
|---|
| 1910 | + { |
|---|
| 1911 | + $params = array(); |
|---|
| 1912 | + foreach (get_object_vars($this) as $k => $v) { |
|---|
| 1913 | + if ($k[0] == '_') { |
|---|
| 1914 | + $params[substr($k, 1)] = $v; |
|---|
| 1915 | + } |
|---|
| 1916 | + } |
|---|
| 1917 | + |
|---|
| 1918 | + return $params; |
|---|
| 1919 | + } |
|---|
| 1920 | + |
|---|
| 1921 | + /** |
|---|
| 1922 | + * Renders a diff. |
|---|
| 1923 | + * |
|---|
| 1924 | + * @param Text_Diff $diff A Text_Diff object. |
|---|
| 1925 | + * |
|---|
| 1926 | + * @return string The formatted output. |
|---|
| 1927 | + */ |
|---|
| 1928 | + function render($diff) |
|---|
| 1929 | + { |
|---|
| 1930 | + $xi = $yi = 1; |
|---|
| 1931 | + $block = false; |
|---|
| 1932 | + $context = array(); |
|---|
| 1933 | + |
|---|
| 1934 | + $nlead = $this->_leading_context_lines; |
|---|
| 1935 | + $ntrail = $this->_trailing_context_lines; |
|---|
| 1936 | + |
|---|
| 1937 | + $output = $this->_startDiff(); |
|---|
| 1938 | + |
|---|
| 1939 | + $diffs = $diff->getDiff(); |
|---|
| 1940 | + foreach ($diffs as $i => $edit) { |
|---|
| 1941 | + /* If these are unchanged (copied) lines, and we want to keep |
|---|
| 1942 | + * leading or trailing context lines, extract them from the copy |
|---|
| 1943 | + * block. */ |
|---|
| 1944 | + if (is_a($edit, 'Text_Diff_Op_copy')) { |
|---|
| 1945 | + /* Do we have any diff blocks yet? */ |
|---|
| 1946 | + if (is_array($block)) { |
|---|
| 1947 | + /* How many lines to keep as context from the copy |
|---|
| 1948 | + * block. */ |
|---|
| 1949 | + $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; |
|---|
| 1950 | + if (count($edit->orig) <= $keep) { |
|---|
| 1951 | + /* We have less lines in the block than we want for |
|---|
| 1952 | + * context => keep the whole block. */ |
|---|
| 1953 | + $block[] = $edit; |
|---|
| 1954 | + } else { |
|---|
| 1955 | + if ($ntrail) { |
|---|
| 1956 | + /* Create a new block with as many lines as we need |
|---|
| 1957 | + * for the trailing context. */ |
|---|
| 1958 | + $context = array_slice($edit->orig, 0, $ntrail); |
|---|
| 1959 | + $block[] = &new Text_Diff_Op_copy($context); |
|---|
| 1960 | + } |
|---|
| 1961 | + /* @todo */ |
|---|
| 1962 | + $output .= $this->_block($x0, $ntrail + $xi - $x0, |
|---|
| 1963 | + $y0, $ntrail + $yi - $y0, |
|---|
| 1964 | + $block); |
|---|
| 1965 | + $block = false; |
|---|
| 1966 | + } |
|---|
| 1967 | + } |
|---|
| 1968 | + /* Keep the copy block as the context for the next block. */ |
|---|
| 1969 | + $context = $edit->orig; |
|---|
| 1970 | + } else { |
|---|
| 1971 | + /* Don't we have any diff blocks yet? */ |
|---|
| 1972 | + if (!is_array($block)) { |
|---|
| 1973 | + /* Extract context lines from the preceding copy block. */ |
|---|
| 1974 | + $context = array_slice($context, count($context) - $nlead); |
|---|
| 1975 | + $x0 = $xi - count($context); |
|---|
| 1976 | + $y0 = $yi - count($context); |
|---|
| 1977 | + $block = array(); |
|---|
| 1978 | + if ($context) { |
|---|
| 1979 | + $block[] = &new Text_Diff_Op_copy($context); |
|---|
| 1980 | + } |
|---|
| 1981 | + } |
|---|
| 1982 | + $block[] = $edit; |
|---|
| 1983 | + } |
|---|
| 1984 | + |
|---|
| 1985 | + if ($edit->orig) { |
|---|
| 1986 | + $xi += count($edit->orig); |
|---|
| 1987 | + } |
|---|
| 1988 | + if ($edit->final) { |
|---|
| 1989 | + $yi += count($edit->final); |
|---|
| 1990 | + } |
|---|
| 1991 | + } |
|---|
| 1992 | + |
|---|
| 1993 | + if (is_array($block)) { |
|---|
| 1994 | + $output .= $this->_block($x0, $xi - $x0, |
|---|
| 1995 | + $y0, $yi - $y0, |
|---|
| 1996 | + $block); |
|---|
| 1997 | + } |
|---|
| 1998 | + |
|---|
| 1999 | + return $output . $this->_endDiff(); |
|---|
| 2000 | + } |
|---|
| 2001 | + |
|---|
| 2002 | + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) |
|---|
| 2003 | + { |
|---|
| 2004 | + $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen)); |
|---|
| 2005 | + |
|---|
| 2006 | + foreach ($edits as $edit) { |
|---|
| 2007 | + switch (strtolower(get_class($edit))) { |
|---|
| 2008 | + case 'text_diff_op_copy': |
|---|
| 2009 | + $output .= $this->_context($edit->orig); |
|---|
| 2010 | + break; |
|---|
| 2011 | + |
|---|
| 2012 | + case 'text_diff_op_add': |
|---|
| 2013 | + $output .= $this->_added($edit->final); |
|---|
| 2014 | + break; |
|---|
| 2015 | + |
|---|
| 2016 | + case 'text_diff_op_delete': |
|---|
| 2017 | + $output .= $this->_deleted($edit->orig); |
|---|
| 2018 | + break; |
|---|
| 2019 | + |
|---|
| 2020 | + case 'text_diff_op_change': |
|---|
| 2021 | + $output .= $this->_changed($edit->orig, $edit->final); |
|---|
| 2022 | + break; |
|---|
| 2023 | + } |
|---|
| 2024 | + } |
|---|
| 2025 | + |
|---|
| 2026 | + return $output . $this->_endBlock(); |
|---|
| 2027 | + } |
|---|
| 2028 | + |
|---|
| 2029 | + function _startDiff() |
|---|
| 2030 | + { |
|---|
| 2031 | + return ''; |
|---|
| 2032 | + } |
|---|
| 2033 | + |
|---|
| 2034 | + function _endDiff() |
|---|
| 2035 | + { |
|---|
| 2036 | + return ''; |
|---|
| 2037 | + } |
|---|
| 2038 | + |
|---|
| 2039 | + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
|---|
| 2040 | + { |
|---|
| 2041 | + if ($xlen > 1) { |
|---|
| 2042 | + $xbeg .= ',' . ($xbeg + $xlen - 1); |
|---|
| 2043 | + } |
|---|
| 2044 | + if ($ylen > 1) { |
|---|
| 2045 | + $ybeg .= ',' . ($ybeg + $ylen - 1); |
|---|
| 2046 | + } |
|---|
| 2047 | + |
|---|
| 2048 | + // this matches the GNU Diff behaviour |
|---|
| 2049 | + if ($xlen && !$ylen) { |
|---|
| 2050 | + $ybeg--; |
|---|
| 2051 | + } elseif (!$xlen) { |
|---|
| 2052 | + $xbeg--; |
|---|
| 2053 | + } |
|---|
| 2054 | + |
|---|
| 2055 | + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; |
|---|
| 2056 | + } |
|---|
| 2057 | + |
|---|
| 2058 | + function _startBlock($header) |
|---|
| 2059 | + { |
|---|
| 2060 | + return $header . "\n"; |
|---|
| 2061 | + } |
|---|
| 2062 | + |
|---|
| 2063 | + function _endBlock() |
|---|
| 2064 | + { |
|---|
| 2065 | + return ''; |
|---|
| 2066 | + } |
|---|
| 2067 | + |
|---|
| 2068 | + function _lines($lines, $prefix = ' ') |
|---|
| 2069 | + { |
|---|
| 2070 | + return $prefix . implode("\n$prefix", $lines) . "\n"; |
|---|
| 2071 | + } |
|---|
| 2072 | + |
|---|
| 2073 | + function _context($lines) |
|---|
| 2074 | + { |
|---|
| 2075 | + return $this->_lines($lines, ' '); |
|---|
| 2076 | + } |
|---|
| 2077 | + |
|---|
| 2078 | + function _added($lines) |
|---|
| 2079 | + { |
|---|
| 2080 | + return $this->_lines($lines, '> '); |
|---|
| 2081 | + } |
|---|
| 2082 | + |
|---|
| 2083 | + function _deleted($lines) |
|---|
| 2084 | + { |
|---|
| 2085 | + return $this->_lines($lines, '< '); |
|---|
| 2086 | + } |
|---|
| 2087 | + |
|---|
| 2088 | + function _changed($orig, $final) |
|---|
| 2089 | + { |
|---|
| 2090 | + return $this->_deleted($orig) . "---\n" . $this->_added($final); |
|---|
| 2091 | + } |
|---|
| 2092 | + |
|---|
| 2093 | +} |
|---|
| 2094 | Index: wp-includes/Text/Diff.php |
|---|
| 2095 | =================================================================== |
|---|
| 2096 | --- wp-includes/Text/Diff.php (revision 0) |
|---|
| 2097 | +++ wp-includes/Text/Diff.php (revision 0) |
|---|
| 2098 | @@ -0,0 +1,413 @@ |
|---|
| 2099 | +<?php |
|---|
| 2100 | +/** |
|---|
| 2101 | + * General API for generating and formatting diffs - the differences between |
|---|
| 2102 | + * two sequences of strings. |
|---|
| 2103 | + * |
|---|
| 2104 | + * The original PHP version of this code was written by Geoffrey T. Dairiki |
|---|
| 2105 | + * <dairiki@dairiki.org>, and is used/adapted with his permission. |
|---|
| 2106 | + * |
|---|
| 2107 | + * $Horde: framework/Text_Diff/Diff.php,v 1.26 2008/01/04 10:07:49 jan Exp $ |
|---|
| 2108 | + * |
|---|
| 2109 | + * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2110 | + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) |
|---|
| 2111 | + * |
|---|
| 2112 | + * See the enclosed file COPYING for license information (LGPL). If you did |
|---|
| 2113 | + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. |
|---|
| 2114 | + * |
|---|
| 2115 | + * @package Text_Diff |
|---|
| 2116 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2117 | + */ |
|---|
| 2118 | +class Text_Diff { |
|---|
| 2119 | + |
|---|
| 2120 | + /** |
|---|
| 2121 | + * Array of changes. |
|---|
| 2122 | + * |
|---|
| 2123 | + * @var array |
|---|
| 2124 | + */ |
|---|
| 2125 | + var $_edits; |
|---|
| 2126 | + |
|---|
| 2127 | + /** |
|---|
| 2128 | + * Computes diffs between sequences of strings. |
|---|
| 2129 | + * |
|---|
| 2130 | + * @param string $engine Name of the diffing engine to use. 'auto' |
|---|
| 2131 | + * will automatically select the best. |
|---|
| 2132 | + * @param array $params Parameters to pass to the diffing engine. |
|---|
| 2133 | + * Normally an array of two arrays, each |
|---|
| 2134 | + * containing the lines from a file. |
|---|
| 2135 | + */ |
|---|
| 2136 | + function Text_Diff($engine, $params) |
|---|
| 2137 | + { |
|---|
| 2138 | + // Backward compatibility workaround. |
|---|
| 2139 | + if (!is_string($engine)) { |
|---|
| 2140 | + $params = array($engine, $params); |
|---|
| 2141 | + $engine = 'auto'; |
|---|
| 2142 | + } |
|---|
| 2143 | + |
|---|
| 2144 | + if ($engine == 'auto') { |
|---|
| 2145 | + $engine = extension_loaded('xdiff') ? 'xdiff' : 'native'; |
|---|
| 2146 | + } else { |
|---|
| 2147 | + $engine = basename($engine); |
|---|
| 2148 | + } |
|---|
| 2149 | + |
|---|
| 2150 | + require_once 'Text/Diff/Engine/' . $engine . '.php'; |
|---|
| 2151 | + $class = 'Text_Diff_Engine_' . $engine; |
|---|
| 2152 | + $diff_engine = new $class(); |
|---|
| 2153 | + |
|---|
| 2154 | + $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); |
|---|
| 2155 | + } |
|---|
| 2156 | + |
|---|
| 2157 | + /** |
|---|
| 2158 | + * Returns the array of differences. |
|---|
| 2159 | + */ |
|---|
| 2160 | + function getDiff() |
|---|
| 2161 | + { |
|---|
| 2162 | + return $this->_edits; |
|---|
| 2163 | + } |
|---|
| 2164 | + |
|---|
| 2165 | + /** |
|---|
| 2166 | + * Computes a reversed diff. |
|---|
| 2167 | + * |
|---|
| 2168 | + * Example: |
|---|
| 2169 | + * <code> |
|---|
| 2170 | + * $diff = new Text_Diff($lines1, $lines2); |
|---|
| 2171 | + * $rev = $diff->reverse(); |
|---|
| 2172 | + * </code> |
|---|
| 2173 | + * |
|---|
| 2174 | + * @return Text_Diff A Diff object representing the inverse of the |
|---|
| 2175 | + * original diff. Note that we purposely don't return a |
|---|
| 2176 | + * reference here, since this essentially is a clone() |
|---|
| 2177 | + * method. |
|---|
| 2178 | + */ |
|---|
| 2179 | + function reverse() |
|---|
| 2180 | + { |
|---|
| 2181 | + if (version_compare(zend_version(), '2', '>')) { |
|---|
| 2182 | + $rev = clone($this); |
|---|
| 2183 | + } else { |
|---|
| 2184 | + $rev = $this; |
|---|
| 2185 | + } |
|---|
| 2186 | + $rev->_edits = array(); |
|---|
| 2187 | + foreach ($this->_edits as $edit) { |
|---|
| 2188 | + $rev->_edits[] = $edit->reverse(); |
|---|
| 2189 | + } |
|---|
| 2190 | + return $rev; |
|---|
| 2191 | + } |
|---|
| 2192 | + |
|---|
| 2193 | + /** |
|---|
| 2194 | + * Checks for an empty diff. |
|---|
| 2195 | + * |
|---|
| 2196 | + * @return boolean True if two sequences were identical. |
|---|
| 2197 | + */ |
|---|
| 2198 | + function isEmpty() |
|---|
| 2199 | + { |
|---|
| 2200 | + foreach ($this->_edits as $edit) { |
|---|
| 2201 | + if (!is_a($edit, 'Text_Diff_Op_copy')) { |
|---|
| 2202 | + return false; |
|---|
| 2203 | + } |
|---|
| 2204 | + } |
|---|
| 2205 | + return true; |
|---|
| 2206 | + } |
|---|
| 2207 | + |
|---|
| 2208 | + /** |
|---|
| 2209 | + * Computes the length of the Longest Common Subsequence (LCS). |
|---|
| 2210 | + * |
|---|
| 2211 | + * This is mostly for diagnostic purposes. |
|---|
| 2212 | + * |
|---|
| 2213 | + * @return integer The length of the LCS. |
|---|
| 2214 | + */ |
|---|
| 2215 | + function lcs() |
|---|
| 2216 | + { |
|---|
| 2217 | + $lcs = 0; |
|---|
| 2218 | + foreach ($this->_edits as $edit) { |
|---|
| 2219 | + if (is_a($edit, 'Text_Diff_Op_copy')) { |
|---|
| 2220 | + $lcs += count($edit->orig); |
|---|
| 2221 | + } |
|---|
| 2222 | + } |
|---|
| 2223 | + return $lcs; |
|---|
| 2224 | + } |
|---|
| 2225 | + |
|---|
| 2226 | + /** |
|---|
| 2227 | + * Gets the original set of lines. |
|---|
| 2228 | + * |
|---|
| 2229 | + * This reconstructs the $from_lines parameter passed to the constructor. |
|---|
| 2230 | + * |
|---|
| 2231 | + * @return array The original sequence of strings. |
|---|
| 2232 | + */ |
|---|
| 2233 | + function getOriginal() |
|---|
| 2234 | + { |
|---|
| 2235 | + $lines = array(); |
|---|
| 2236 | + foreach ($this->_edits as $edit) { |
|---|
| 2237 | + if ($edit->orig) { |
|---|
| 2238 | + array_splice($lines, count($lines), 0, $edit->orig); |
|---|
| 2239 | + } |
|---|
| 2240 | + } |
|---|
| 2241 | + return $lines; |
|---|
| 2242 | + } |
|---|
| 2243 | + |
|---|
| 2244 | + /** |
|---|
| 2245 | + * Gets the final set of lines. |
|---|
| 2246 | + * |
|---|
| 2247 | + * This reconstructs the $to_lines parameter passed to the constructor. |
|---|
| 2248 | + * |
|---|
| 2249 | + * @return array The sequence of strings. |
|---|
| 2250 | + */ |
|---|
| 2251 | + function getFinal() |
|---|
| 2252 | + { |
|---|
| 2253 | + $lines = array(); |
|---|
| 2254 | + foreach ($this->_edits as $edit) { |
|---|
| 2255 | + if ($edit->final) { |
|---|
| 2256 | + array_splice($lines, count($lines), 0, $edit->final); |
|---|
| 2257 | + } |
|---|
| 2258 | + } |
|---|
| 2259 | + return $lines; |
|---|
| 2260 | + } |
|---|
| 2261 | + |
|---|
| 2262 | + /** |
|---|
| 2263 | + * Removes trailing newlines from a line of text. This is meant to be used |
|---|
| 2264 | + * with array_walk(). |
|---|
| 2265 | + * |
|---|
| 2266 | + * @param string $line The line to trim. |
|---|
| 2267 | + * @param integer $key The index of the line in the array. Not used. |
|---|
| 2268 | + */ |
|---|
| 2269 | + function trimNewlines(&$line, $key) |
|---|
| 2270 | + { |
|---|
| 2271 | + $line = str_replace(array("\n", "\r"), '', $line); |
|---|
| 2272 | + } |
|---|
| 2273 | + |
|---|
| 2274 | + /** |
|---|
| 2275 | + * Determines the location of the system temporary directory. |
|---|
| 2276 | + * |
|---|
| 2277 | + * @static |
|---|
| 2278 | + * |
|---|
| 2279 | + * @access protected |
|---|
| 2280 | + * |
|---|
| 2281 | + * @return string A directory name which can be used for temp files. |
|---|
| 2282 | + * Returns false if one could not be found. |
|---|
| 2283 | + */ |
|---|
| 2284 | + function _getTempDir() |
|---|
| 2285 | + { |
|---|
| 2286 | + $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp', |
|---|
| 2287 | + 'c:\windows\temp', 'c:\winnt\temp'); |
|---|
| 2288 | + |
|---|
| 2289 | + /* Try PHP's upload_tmp_dir directive. */ |
|---|
| 2290 | + $tmp = ini_get('upload_tmp_dir'); |
|---|
| 2291 | + |
|---|
| 2292 | + /* Otherwise, try to determine the TMPDIR environment variable. */ |
|---|
| 2293 | + if (!strlen($tmp)) { |
|---|
| 2294 | + $tmp = getenv('TMPDIR'); |
|---|
| 2295 | + } |
|---|
| 2296 | + |
|---|
| 2297 | + /* If we still cannot determine a value, then cycle through a list of |
|---|
| 2298 | + * preset possibilities. */ |
|---|
| 2299 | + while (!strlen($tmp) && count($tmp_locations)) { |
|---|
| 2300 | + $tmp_check = array_shift($tmp_locations); |
|---|
| 2301 | + if (@is_dir($tmp_check)) { |
|---|
| 2302 | + $tmp = $tmp_check; |
|---|
| 2303 | + } |
|---|
| 2304 | + } |
|---|
| 2305 | + |
|---|
| 2306 | + /* If it is still empty, we have failed, so return false; otherwise |
|---|
| 2307 | + * return the directory determined. */ |
|---|
| 2308 | + return strlen($tmp) ? $tmp : false; |
|---|
| 2309 | + } |
|---|
| 2310 | + |
|---|
| 2311 | + /** |
|---|
| 2312 | + * Checks a diff for validity. |
|---|
| 2313 | + * |
|---|
| 2314 | + * This is here only for debugging purposes. |
|---|
| 2315 | + */ |
|---|
| 2316 | + function _check($from_lines, $to_lines) |
|---|
| 2317 | + { |
|---|
| 2318 | + if (serialize($from_lines) != serialize($this->getOriginal())) { |
|---|
| 2319 | + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); |
|---|
| 2320 | + } |
|---|
| 2321 | + if (serialize($to_lines) != serialize($this->getFinal())) { |
|---|
| 2322 | + trigger_error("Reconstructed final doesn't match", E_USER_ERROR); |
|---|
| 2323 | + } |
|---|
| 2324 | + |
|---|
| 2325 | + $rev = $this->reverse(); |
|---|
| 2326 | + if (serialize($to_lines) != serialize($rev->getOriginal())) { |
|---|
| 2327 | + trigger_error("Reversed original doesn't match", E_USER_ERROR); |
|---|
| 2328 | + } |
|---|
| 2329 | + if (serialize($from_lines) != serialize($rev->getFinal())) { |
|---|
| 2330 | + trigger_error("Reversed final doesn't match", E_USER_ERROR); |
|---|
| 2331 | + } |
|---|
| 2332 | + |
|---|
| 2333 | + $prevtype = null; |
|---|
| 2334 | + foreach ($this->_edits as $edit) { |
|---|
| 2335 | + if ($prevtype == get_class($edit)) { |
|---|
| 2336 | + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); |
|---|
| 2337 | + } |
|---|
| 2338 | + $prevtype = get_class($edit); |
|---|
| 2339 | + } |
|---|
| 2340 | + |
|---|
| 2341 | + return true; |
|---|
| 2342 | + } |
|---|
| 2343 | + |
|---|
| 2344 | +} |
|---|
| 2345 | + |
|---|
| 2346 | +/** |
|---|
| 2347 | + * @package Text_Diff |
|---|
| 2348 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2349 | + */ |
|---|
| 2350 | +class Text_MappedDiff extends Text_Diff { |
|---|
| 2351 | + |
|---|
| 2352 | + /** |
|---|
| 2353 | + * Computes a diff between sequences of strings. |
|---|
| 2354 | + * |
|---|
| 2355 | + * This can be used to compute things like case-insensitve diffs, or diffs |
|---|
| 2356 | + * which ignore changes in white-space. |
|---|
| 2357 | + * |
|---|
| 2358 | + * @param array $from_lines An array of strings. |
|---|
| 2359 | + * @param array $to_lines An array of strings. |
|---|
| 2360 | + * @param array $mapped_from_lines This array should have the same size |
|---|
| 2361 | + * number of elements as $from_lines. The |
|---|
| 2362 | + * elements in $mapped_from_lines and |
|---|
| 2363 | + * $mapped_to_lines are what is actually |
|---|
| 2364 | + * compared when computing the diff. |
|---|
| 2365 | + * @param array $mapped_to_lines This array should have the same number |
|---|
| 2366 | + * of elements as $to_lines. |
|---|
| 2367 | + */ |
|---|
| 2368 | + function Text_MappedDiff($from_lines, $to_lines, |
|---|
| 2369 | + $mapped_from_lines, $mapped_to_lines) |
|---|
| 2370 | + { |
|---|
| 2371 | + assert(count($from_lines) == count($mapped_from_lines)); |
|---|
| 2372 | + assert(count($to_lines) == count($mapped_to_lines)); |
|---|
| 2373 | + |
|---|
| 2374 | + parent::Text_Diff($mapped_from_lines, $mapped_to_lines); |
|---|
| 2375 | + |
|---|
| 2376 | + $xi = $yi = 0; |
|---|
| 2377 | + for ($i = 0; $i < count($this->_edits); $i++) { |
|---|
| 2378 | + $orig = &$this->_edits[$i]->orig; |
|---|
| 2379 | + if (is_array($orig)) { |
|---|
| 2380 | + $orig = array_slice($from_lines, $xi, count($orig)); |
|---|
| 2381 | + $xi += count($orig); |
|---|
| 2382 | + } |
|---|
| 2383 | + |
|---|
| 2384 | + $final = &$this->_edits[$i]->final; |
|---|
| 2385 | + if (is_array($final)) { |
|---|
| 2386 | + $final = array_slice($to_lines, $yi, count($final)); |
|---|
| 2387 | + $yi += count($final); |
|---|
| 2388 | + } |
|---|
| 2389 | + } |
|---|
| 2390 | + } |
|---|
| 2391 | + |
|---|
| 2392 | +} |
|---|
| 2393 | + |
|---|
| 2394 | +/** |
|---|
| 2395 | + * @package Text_Diff |
|---|
| 2396 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2397 | + * |
|---|
| 2398 | + * @access private |
|---|
| 2399 | + */ |
|---|
| 2400 | +class Text_Diff_Op { |
|---|
| 2401 | + |
|---|
| 2402 | + var $orig; |
|---|
| 2403 | + var $final; |
|---|
| 2404 | + |
|---|
| 2405 | + function &reverse() |
|---|
| 2406 | + { |
|---|
| 2407 | + trigger_error('Abstract method', E_USER_ERROR); |
|---|
| 2408 | + } |
|---|
| 2409 | + |
|---|
| 2410 | + function norig() |
|---|
| 2411 | + { |
|---|
| 2412 | + return $this->orig ? count($this->orig) : 0; |
|---|
| 2413 | + } |
|---|
| 2414 | + |
|---|
| 2415 | + function nfinal() |
|---|
| 2416 | + { |
|---|
| 2417 | + return $this->final ? count($this->final) : 0; |
|---|
| 2418 | + } |
|---|
| 2419 | + |
|---|
| 2420 | +} |
|---|
| 2421 | + |
|---|
| 2422 | +/** |
|---|
| 2423 | + * @package Text_Diff |
|---|
| 2424 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2425 | + * |
|---|
| 2426 | + * @access private |
|---|
| 2427 | + */ |
|---|
| 2428 | +class Text_Diff_Op_copy extends Text_Diff_Op { |
|---|
| 2429 | + |
|---|
| 2430 | + function Text_Diff_Op_copy($orig, $final = false) |
|---|
| 2431 | + { |
|---|
| 2432 | + if (!is_array($final)) { |
|---|
| 2433 | + $final = $orig; |
|---|
| 2434 | + } |
|---|
| 2435 | + $this->orig = $orig; |
|---|
| 2436 | + $this->final = $final; |
|---|
| 2437 | + } |
|---|
| 2438 | + |
|---|
| 2439 | + function &reverse() |
|---|
| 2440 | + { |
|---|
| 2441 | + $reverse = &new Text_Diff_Op_copy($this->final, $this->orig); |
|---|
| 2442 | + return $reverse; |
|---|
| 2443 | + } |
|---|
| 2444 | + |
|---|
| 2445 | +} |
|---|
| 2446 | + |
|---|
| 2447 | +/** |
|---|
| 2448 | + * @package Text_Diff |
|---|
| 2449 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2450 | + * |
|---|
| 2451 | + * @access private |
|---|
| 2452 | + */ |
|---|
| 2453 | +class Text_Diff_Op_delete extends Text_Diff_Op { |
|---|
| 2454 | + |
|---|
| 2455 | + function Text_Diff_Op_delete($lines) |
|---|
| 2456 | + { |
|---|
| 2457 | + $this->orig = $lines; |
|---|
| 2458 | + $this->final = false; |
|---|
| 2459 | + } |
|---|
| 2460 | + |
|---|
| 2461 | + function &reverse() |
|---|
| 2462 | + { |
|---|
| 2463 | + $reverse = &new Text_Diff_Op_add($this->orig); |
|---|
| 2464 | + return $reverse; |
|---|
| 2465 | + } |
|---|
| 2466 | + |
|---|
| 2467 | +} |
|---|
| 2468 | + |
|---|
| 2469 | +/** |
|---|
| 2470 | + * @package Text_Diff |
|---|
| 2471 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2472 | + * |
|---|
| 2473 | + * @access private |
|---|
| 2474 | + */ |
|---|
| 2475 | +class Text_Diff_Op_add extends Text_Diff_Op { |
|---|
| 2476 | + |
|---|
| 2477 | + function Text_Diff_Op_add($lines) |
|---|
| 2478 | + { |
|---|
| 2479 | + $this->final = $lines; |
|---|
| 2480 | + $this->orig = false; |
|---|
| 2481 | + } |
|---|
| 2482 | + |
|---|
| 2483 | + function &reverse() |
|---|
| 2484 | + { |
|---|
| 2485 | + $reverse = &new Text_Diff_Op_delete($this->final); |
|---|
| 2486 | + return $reverse; |
|---|
| 2487 | + } |
|---|
| 2488 | + |
|---|
| 2489 | +} |
|---|
| 2490 | + |
|---|
| 2491 | +/** |
|---|
| 2492 | + * @package Text_Diff |
|---|
| 2493 | + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
|---|
| 2494 | + * |
|---|
| 2495 | + * @access private |
|---|
| 2496 | + */ |
|---|
| 2497 | +class Text_Diff_Op_change extends Text_Diff_Op { |
|---|
| 2498 | + |
|---|
| 2499 | + function Text_Diff_Op_change($orig, $final) |
|---|
| 2500 | + { |
|---|
| 2501 | + $this->orig = $orig; |
|---|
| 2502 | + $this->final = $final; |
|---|
| 2503 | + } |
|---|
| 2504 | + |
|---|
| 2505 | + function &reverse() |
|---|
| 2506 | + { |
|---|
| 2507 | + $reverse = &new Text_Diff_Op_change($this->final, $this->orig); |
|---|
| 2508 | + return $reverse; |
|---|
| 2509 | + } |
|---|
| 2510 | + |
|---|
| 2511 | +} |
|---|
| 2512 | Index: wp-includes/pluggable.php |
|---|
| 2513 | =================================================================== |
|---|
| 2514 | --- wp-includes/pluggable.php (revision 7746) |
|---|
| 2515 | +++ wp-includes/pluggable.php (working copy) |
|---|
| 2516 | @@ -1350,4 +1350,68 @@ |
|---|
| 2517 | } |
|---|
| 2518 | endif; |
|---|
| 2519 | |
|---|
| 2520 | +if ( !function_exists( 'wp_text_diff' ) ) : |
|---|
| 2521 | +/** |
|---|
| 2522 | + * wp_text_diff() - compares two strings and outputs a human readable HTML representation of their difference |
|---|
| 2523 | + * |
|---|
| 2524 | + * Basically a wrapper for man diff(1) |
|---|
| 2525 | + * |
|---|
| 2526 | + * Must accept an optional third parameter, $args @see wp_parse_args() |
|---|
| 2527 | + * (string) title: optional. If present, titles the diff in a manner compatible with the output |
|---|
| 2528 | + * |
|---|
| 2529 | + * Must return the empty string if the two compared strings are found to be equivalent according to whatever metric |
|---|
| 2530 | + * |
|---|
| 2531 | + * @since 2.6 |
|---|
| 2532 | + * @uses Text_Diff |
|---|
| 2533 | + * @uses WP_Text_Diff_Renderer_Table |
|---|
| 2534 | + * |
|---|
| 2535 | + * @param string $left_string "old" (left) version of string |
|---|
| 2536 | + * @param string $right_string "new" (right) version of string |
|---|
| 2537 | + * @param string|array $args @see wp_parse_args() |
|---|
| 2538 | + * @return string human readable HTML of string differences. Empty string if strings are equivalent |
|---|
| 2539 | + */ |
|---|
| 2540 | +function wp_text_diff( $left_string, $right_string, $args = null ) { |
|---|
| 2541 | + $defaults = array( 'title' => '' ); |
|---|
| 2542 | + $args = wp_parse_args( $args, $defaults ); |
|---|
| 2543 | + |
|---|
| 2544 | + // PEAR Text_Diff is lame; it includes things from include_path rather than it's own path. |
|---|
| 2545 | + // Not sure of the ramifications of disttributing modified code. |
|---|
| 2546 | + ini_set('include_path', '.' . PATH_SEPARATOR . ABSPATH . WPINC ); |
|---|
| 2547 | + |
|---|
| 2548 | + if ( !class_exists( 'WP_Text_Diff_Renderer_Table' ) ) |
|---|
| 2549 | + require( ABSPATH . WPINC . '/wp-diff.php' ); |
|---|
| 2550 | + |
|---|
| 2551 | + // Normalize whitespace |
|---|
| 2552 | + $left_string = trim($left_string); |
|---|
| 2553 | + $right_string = trim($right_string); |
|---|
| 2554 | + $left_string = str_replace("\r", "\n", $left_string); |
|---|
| 2555 | + $right_string = str_replace("\r", "\n", $right_string); |
|---|
| 2556 | + $left_string = preg_replace( array( '/\n+/', '/[ \t]+/' ), array( "\n", ' ' ), $left_string ); |
|---|
| 2557 | + $right_string = preg_replace( array( '/\n+/', '/[ \t]+/' ), array( "\n", ' ' ), $right_string ); |
|---|
| 2558 | + |
|---|
| 2559 | + $left_lines = split("\n", $left_string); |
|---|
| 2560 | + $right_lines = split("\n", $right_string); |
|---|
| 2561 | + |
|---|
| 2562 | + $text_diff = new Text_Diff($left_lines, $right_lines); |
|---|
| 2563 | + $renderer = new WP_Text_Diff_Renderer_Table(); |
|---|
| 2564 | + $diff = $renderer->render($text_diff); |
|---|
| 2565 | + |
|---|
| 2566 | + ini_restore('include_path'); |
|---|
| 2567 | + |
|---|
| 2568 | + if ( !$diff ) |
|---|
| 2569 | + return ''; |
|---|
| 2570 | + |
|---|
| 2571 | + $r = "<table class='diff'>\n"; |
|---|
| 2572 | + $r .= "<col class='ltype' /><col class='content' /><col class='ltype' /><col class='content' />"; |
|---|
| 2573 | + |
|---|
| 2574 | + if ( $args['title'] ) |
|---|
| 2575 | + $r .= "<thead><tr><th colspan='4'>$args[title]</th></tr></thead>\n"; |
|---|
| 2576 | + |
|---|
| 2577 | + $r .= "<tbody>\n$diff\n</tbody>\n"; |
|---|
| 2578 | + $r .= "</table>"; |
|---|
| 2579 | + |
|---|
| 2580 | + return $r; |
|---|
| 2581 | +} |
|---|
| 2582 | +endif; |
|---|
| 2583 | + |
|---|
| 2584 | ?> |
|---|
| 2585 | Index: wp-admin/admin-ajax.php |
|---|
| 2586 | =================================================================== |
|---|
| 2587 | --- wp-admin/admin-ajax.php (revision 7746) |
|---|
| 2588 | +++ wp-admin/admin-ajax.php (working copy) |
|---|
| 2589 | @@ -460,6 +460,8 @@ |
|---|
| 2590 | $x->send(); |
|---|
| 2591 | break; |
|---|
| 2592 | case 'autosave' : // The name of this action is hardcoded in edit_post() |
|---|
| 2593 | + define( 'DOING_AUTOSAVE', true ); |
|---|
| 2594 | + |
|---|
| 2595 | $nonce_age = check_ajax_referer( 'autosave', 'autosavenonce'); |
|---|
| 2596 | global $current_user; |
|---|
| 2597 | |
|---|
| 2598 | Index: wp-admin/wp-admin.css |
|---|
| 2599 | =================================================================== |
|---|
| 2600 | --- wp-admin/wp-admin.css (revision 7746) |
|---|
| 2601 | +++ wp-admin/wp-admin.css (working copy) |
|---|
| 2602 | @@ -894,6 +894,21 @@ |
|---|
| 2603 | margin-right: 5px |
|---|
| 2604 | } |
|---|
| 2605 | |
|---|
| 2606 | +.form-table pre { |
|---|
| 2607 | + padding: 8px; |
|---|
| 2608 | + margin: 0; |
|---|
| 2609 | + /* http://www.longren.org/2006/09/27/wrapping-text-inside-pre-tags/ */ |
|---|
| 2610 | + white-space: pre-wrap; /* css-3 */ |
|---|
| 2611 | + white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ |
|---|
| 2612 | + white-space: -pre-wrap; /* Opera 4-6 */ |
|---|
| 2613 | + white-space: -o-pre-wrap; /* Opera 7 */ |
|---|
| 2614 | + word-wrap: break-word; /* Internet Explorer 5.5+ */ |
|---|
| 2615 | +} |
|---|
| 2616 | + |
|---|
| 2617 | +table.form-table td .updated { |
|---|
| 2618 | + font-size: 13px; |
|---|
| 2619 | +} |
|---|
| 2620 | + |
|---|
| 2621 | /* Post Screen */ |
|---|
| 2622 | |
|---|
| 2623 | #tagsdiv #newtag { |
|---|
| 2624 | @@ -1464,3 +1479,27 @@ |
|---|
| 2625 | .hide-if-no-js { |
|---|
| 2626 | display: none; |
|---|
| 2627 | } |
|---|
| 2628 | + |
|---|
| 2629 | +/* Diff */ |
|---|
| 2630 | + |
|---|
| 2631 | +table.diff { |
|---|
| 2632 | + width: 100%; |
|---|
| 2633 | +} |
|---|
| 2634 | + |
|---|
| 2635 | +table.diff col.content { |
|---|
| 2636 | + width: 50%; |
|---|
| 2637 | +} |
|---|
| 2638 | + |
|---|
| 2639 | +table.diff tr { |
|---|
| 2640 | + background-color: transparent; |
|---|
| 2641 | +} |
|---|
| 2642 | + |
|---|
| 2643 | +table.diff td, table.diff th { |
|---|
| 2644 | + padding: .5em; |
|---|
| 2645 | + font-family: monospace; |
|---|
| 2646 | + border: none; |
|---|
| 2647 | +} |
|---|
| 2648 | + |
|---|
| 2649 | +table.diff .diff-deletedline del, table.diff .diff-addedline ins { |
|---|
| 2650 | + text-decoration: none; |
|---|
| 2651 | +} |
|---|
| 2652 | Index: wp-admin/post.php |
|---|
| 2653 | =================================================================== |
|---|
| 2654 | --- wp-admin/post.php (revision 7746) |
|---|
| 2655 | +++ wp-admin/post.php (working copy) |
|---|
| 2656 | @@ -77,8 +77,8 @@ |
|---|
| 2657 | |
|---|
| 2658 | if ( empty($post->ID) ) wp_die( __("You attempted to edit a post that doesn't exist. Perhaps it was deleted?") ); |
|---|
| 2659 | |
|---|
| 2660 | - if ( 'page' == $post->post_type ) { |
|---|
| 2661 | - wp_redirect("page.php?action=edit&post=$post_ID"); |
|---|
| 2662 | + if ( 'post' != $post->post_type ) { |
|---|
| 2663 | + wp_redirect( get_edit_post_link( $post->ID, 'url' ) ); |
|---|
| 2664 | exit(); |
|---|
| 2665 | } |
|---|
| 2666 | |
|---|
| 2667 | Index: wp-admin/revision.php |
|---|
| 2668 | =================================================================== |
|---|
| 2669 | --- wp-admin/revision.php (revision 0) |
|---|
| 2670 | +++ wp-admin/revision.php (revision 0) |
|---|
| 2671 | @@ -0,0 +1,132 @@ |
|---|
| 2672 | +<?php |
|---|
| 2673 | + |
|---|
| 2674 | +require_once('admin.php'); |
|---|
| 2675 | + |
|---|
| 2676 | +$parent_file = 'edit.php'; |
|---|
| 2677 | +$submenu_file = 'edit.php'; |
|---|
| 2678 | + |
|---|
| 2679 | +wp_reset_vars(array('revision', 'diff', 'restore')); |
|---|
| 2680 | + |
|---|
| 2681 | +$revision_id = absint($revision); |
|---|
| 2682 | +$diff = absint($diff); |
|---|
| 2683 | + |
|---|
| 2684 | +if ( $diff ) { |
|---|
| 2685 | + $restore = false; |
|---|
| 2686 | + $revision = get_post( $revision_id ); |
|---|
| 2687 | + $post = 'revision' == $revision->post_type ? get_post( $revision->post_parent ) : get_post( $revision_id ); |
|---|
| 2688 | + $left_revision = get_post( $diff ); |
|---|
| 2689 | + |
|---|
| 2690 | + // Don't allow reverse diffs? |
|---|
| 2691 | + if ( strtotime($revision->post_modified_gmt) < strtotime($left_revision->post_modified_gmt) ) { |
|---|
| 2692 | + wp_redirect( add_query_arg( array( 'diff' => $revision->ID, 'revision' => $diff ) ) ); |
|---|
| 2693 | + exit; |
|---|
| 2694 | + } |
|---|
| 2695 | + |
|---|
| 2696 | + $h2 = __( 'Compare Revisions of “%1$s”' ); |
|---|
| 2697 | + $right = $revision->ID; |
|---|
| 2698 | + $left = $left_revision->ID; |
|---|
| 2699 | + |
|---|
| 2700 | + if ( |
|---|
| 2701 | + // They're the same |
|---|
| 2702 | + $left_revision->ID == $revision->ID |
|---|
| 2703 | + || |
|---|
| 2704 | + // They don't have a comment parent (and we're not comparing a revision to it's post) |
|---|
| 2705 | + ( $left_revision->ID != $revision->post_parent && $left_revision->post_parent != $revision->ID && $left_revision->post_parent != $revision->post_parent ) |
|---|
| 2706 | + || |
|---|
| 2707 | + // Neither is a revision |
|---|
| 2708 | + ( !wp_get_revision( $left_revision->ID ) && !wp_get_revision( $revision->ID ) ) |
|---|
| 2709 | + ) { |
|---|
| 2710 | + wp_redirect( get_edit_post_link( $revision->ID, 'url' ) ); |
|---|
| 2711 | + exit(); |
|---|
| 2712 | + } |
|---|
| 2713 | +} else { |
|---|
| 2714 | + $revision = wp_get_revision( $revision_id ); |
|---|
| 2715 | + $post = get_post( $revision->post_parent ); |
|---|
| 2716 | + $h2 = __( 'Post Revision for “%1$s” created on %2$s' ); |
|---|
| 2717 | + $right = $post->ID; |
|---|
| 2718 | + $left = $revision->ID; |
|---|
| 2719 | +} |
|---|
| 2720 | + |
|---|
| 2721 | +if ( !$revision || !$post ) { |
|---|
| 2722 | + wp_redirect("edit.php"); |
|---|
| 2723 | + exit(); |
|---|
| 2724 | +} |
|---|
| 2725 | + |
|---|
| 2726 | +if ( $restore && current_user_can( 'edit_post', $revision->post_parent ) ) { |
|---|
| 2727 | + check_admin_referer( "restore-post_$post->ID|$revision->ID" ); |
|---|
| 2728 | + wp_restore_revision( $revision->ID ); |
|---|
| 2729 | + wp_redirect( add_query_arg( array( 'message' => 5, 'revision' => $revision->ID ), get_edit_post_link( $post->ID, 'url' ) ) ); |
|---|
| 2730 | + exit; |
|---|
| 2731 | +} |
|---|
| 2732 | + |
|---|
| 2733 | +add_filter( '_wp_revision_field_post_author', 'get_author_name' ); |
|---|
| 2734 | + |
|---|
| 2735 | +$title = __( 'Post Revision' ); |
|---|
| 2736 | + |
|---|
| 2737 | +require_once( 'admin-header.php' ); |
|---|
| 2738 | + |
|---|
| 2739 | +$post_title = '<a href="' . get_edit_post_link() . '">' . get_the_title() . '</a>'; |
|---|
| 2740 | +$revision_time = wp_post_revision_time( $revision ); |
|---|
| 2741 | +?> |
|---|
| 2742 | + |
|---|
| 2743 | +<div class="wrap"> |
|---|
| 2744 | + |
|---|
| 2745 | +<h2 style="padding-right: 0"><?php printf( $h2, $post_title, $revision_time ); ?></h2> |
|---|
| 2746 | + |
|---|
| 2747 | +<table class="form-table ie-fixed"> |
|---|
| 2748 | + <col class="th" /> |
|---|
| 2749 | +<?php if ( $diff ) : ?> |
|---|
| 2750 | + |
|---|
| 2751 | +<tr id="revision"> |
|---|
| 2752 | + <th scope="row"></th> |
|---|
| 2753 | + <th scope="col" class="th-full"><?php printf( __('Older: %s'), wp_post_revision_time( $left_revision ) ); ?></td> |
|---|
| 2754 | + <th scope="col" class="th-full"><?php printf( __('Newer: %s'), wp_post_revision_time( $revision ) ); ?></td> |
|---|
| 2755 | +</tr> |
|---|
| 2756 | + |
|---|
| 2757 | +<?php endif; |
|---|
| 2758 | + |
|---|
| 2759 | +// use get_post_to_edit ? |
|---|
| 2760 | +$identical = true; |
|---|
| 2761 | +foreach ( _wp_revision_fields() as $field => $field_title ) : |
|---|
| 2762 | + if ( !$diff ) |
|---|
| 2763 | + add_filter( "_wp_revision_field_$field", 'htmlspecialchars' ); |
|---|
| 2764 | + $content = apply_filters( "_wp_revision_field_$field", $revision->$field, $field ); |
|---|
| 2765 | + if ( $diff ) { |
|---|
| 2766 | + $left_content = apply_filters( "_wp_revision_field_$field", $left_revision->$field, $field ); |
|---|
| 2767 | + if ( !$content = wp_text_diff( $left_content, $content ) ) |
|---|
| 2768 | + continue; |
|---|
| 2769 | + } |
|---|
| 2770 | + $identical = false; |
|---|
| 2771 | + ?> |
|---|
| 2772 | + |
|---|
| 2773 | + <tr id="revision-field-<?php echo $field; ?>"?> |
|---|
| 2774 | + <th scope="row"><?php echo wp_specialchars( $field_title ); ?></th> |
|---|
| 2775 | + <td colspan="2"><pre><?php echo $content; ?></pre></td> |
|---|
| 2776 | + </tr> |
|---|
| 2777 | + |
|---|
| 2778 | + <?php |
|---|
| 2779 | + |
|---|
| 2780 | +endforeach; |
|---|
| 2781 | + |
|---|
| 2782 | +if ( $diff && $identical ) : |
|---|
| 2783 | + |
|---|
| 2784 | + ?> |
|---|
| 2785 | + |
|---|
| 2786 | + <tr><td colspan="3"><div class="updated"><p><?php _e( 'These revisions are identical' ); ?></p></div></td></tr> |
|---|
| 2787 | + |
|---|
| 2788 | + <?php |
|---|
| 2789 | + |
|---|
| 2790 | +endif; |
|---|
| 2791 | + |
|---|
| 2792 | +?> |
|---|
| 2793 | + |
|---|
| 2794 | +</table> |
|---|
| 2795 | + |
|---|
| 2796 | +<br class="clear" /> |
|---|
| 2797 | + |
|---|
| 2798 | +<h2><?php _e( 'Post Revisions' ); ?></h2> |
|---|
| 2799 | + |
|---|
| 2800 | +<?php |
|---|
| 2801 | + wp_list_post_revisions( $post, array( 'format' => 'form-table', 'exclude' => $revision->ID, 'parent' => true, 'right' => $right, 'left' => $left ) ); |
|---|
| 2802 | + |
|---|
| 2803 | + require_once( 'admin-footer.php' ); |
|---|
| 2804 | Index: wp-admin/edit-form-advanced.php |
|---|
| 2805 | =================================================================== |
|---|
| 2806 | --- wp-admin/edit-form-advanced.php (revision 7746) |
|---|
| 2807 | +++ wp-admin/edit-form-advanced.php (working copy) |
|---|
| 2808 | @@ -6,6 +6,7 @@ |
|---|
| 2809 | $messages[2] = __('Custom field updated.'); |
|---|
| 2810 | $messages[3] = __('Custom field deleted.'); |
|---|
| 2811 | $messages[4] = __('Post updated.'); |
|---|
| 2812 | +$messages[5] = sprintf( __('Post restored to revision from %s'), wp_post_revision_time( $_GET['revision'] ) ); |
|---|
| 2813 | ?> |
|---|
| 2814 | <?php if (isset($_GET['message'])) : ?> |
|---|
| 2815 | <div id="message" class="updated fade"><p><?php echo $messages[$_GET['message']]; ?></p></div> |
|---|
| 2816 | @@ -336,6 +337,15 @@ |
|---|
| 2817 | </div> |
|---|
| 2818 | <?php endif; ?> |
|---|
| 2819 | |
|---|
| 2820 | +<?php if ( isset($post_ID) && 0 < $post_ID && wp_get_post_revisions( $post_ID ) ) : ?> |
|---|
| 2821 | +<div id="revisionsdiv" class="postbox <?php echo postbox_classes('revisionsdiv', 'post'); ?>"> |
|---|
| 2822 | +<h3><?php _e('Post Revisions'); ?></h3> |
|---|
| 2823 | +<div class="inside"> |
|---|
| 2824 | +<?php wp_list_post_revisions(); ?> |
|---|
| 2825 | +</div> |
|---|
| 2826 | +</div> |
|---|
| 2827 | +<?php endif; ?> |
|---|
| 2828 | + |
|---|
| 2829 | <?php do_meta_boxes('post', 'advanced', $post); ?> |
|---|
| 2830 | |
|---|
| 2831 | <?php do_action('dbx_post_sidebar'); ?> |
|---|
| 2832 | Index: wp-admin/css/ie.css |
|---|
| 2833 | =================================================================== |
|---|
| 2834 | --- wp-admin/css/ie.css (revision 7746) |
|---|
| 2835 | +++ wp-admin/css/ie.css (working copy) |
|---|
| 2836 | @@ -111,7 +111,10 @@ |
|---|
| 2837 | .tablenav-pages { |
|---|
| 2838 | display: block; |
|---|
| 2839 | margin-top: -3px; |
|---|
| 2840 | +} |
|---|
| 2841 | |
|---|
| 2842 | +table.ie-fixed { |
|---|
| 2843 | + table-layout: fixed; |
|---|
| 2844 | } |
|---|
| 2845 | |
|---|
| 2846 | #post-search .button, #widget-search .button { |
|---|
| 2847 | Index: wp-admin/css/colors-fresh.css |
|---|
| 2848 | =================================================================== |
|---|
| 2849 | --- wp-admin/css/colors-fresh.css (revision 7746) |
|---|
| 2850 | +++ wp-admin/css/colors-fresh.css (working copy) |
|---|
| 2851 | @@ -2,7 +2,7 @@ |
|---|
| 2852 | border-color: #999; |
|---|
| 2853 | } |
|---|
| 2854 | |
|---|
| 2855 | -body { |
|---|
| 2856 | +body, .form-table pre { |
|---|
| 2857 | background-color: #fff; |
|---|
| 2858 | color: #333; |
|---|
| 2859 | } |
|---|
| 2860 | @@ -684,3 +684,18 @@ |
|---|
| 2861 | background-color: #ddd; |
|---|
| 2862 | color: #333; |
|---|
| 2863 | } |
|---|
| 2864 | + |
|---|
| 2865 | +/* Diff */ |
|---|
| 2866 | + |
|---|
| 2867 | +table.diff .diff-deletedline { |
|---|
| 2868 | + background-color: #ffdddd; |
|---|
| 2869 | +} |
|---|
| 2870 | +table.diff .diff-deletedline del { |
|---|
| 2871 | + background-color: #ff9999; |
|---|
| 2872 | +} |
|---|
| 2873 | +table.diff .diff-addedline { |
|---|
| 2874 | + background-color: #ddffdd; |
|---|
| 2875 | +} |
|---|
| 2876 | +table.diff .diff-addedline ins { |
|---|
| 2877 | + background-color: #99ff99; |
|---|
| 2878 | +} |
|---|
| 2879 | Index: wp-admin/css/colors-classic.css |
|---|
| 2880 | =================================================================== |
|---|
| 2881 | --- wp-admin/css/colors-classic.css (revision 7746) |
|---|
| 2882 | +++ wp-admin/css/colors-classic.css (working copy) |
|---|
| 2883 | @@ -2,7 +2,7 @@ |
|---|
| 2884 | border-color: #999; |
|---|
| 2885 | } |
|---|
| 2886 | |
|---|
| 2887 | -body { |
|---|
| 2888 | +body, .form-table pre { |
|---|
| 2889 | background-color: #fff; |
|---|
| 2890 | color: #333; |
|---|
| 2891 | } |
|---|
| 2892 | @@ -713,3 +713,18 @@ |
|---|
| 2893 | background-color: #ddd; |
|---|
| 2894 | color: #333; |
|---|
| 2895 | } |
|---|
| 2896 | + |
|---|
| 2897 | +/* Diff */ |
|---|
| 2898 | + |
|---|
| 2899 | +table.diff .diff-deletedline { |
|---|
| 2900 | + background-color: #ffdddd; |
|---|
| 2901 | +} |
|---|
| 2902 | +table.diff .diff-deletedline del { |
|---|
| 2903 | + background-color: #ff9999; |
|---|
| 2904 | +} |
|---|
| 2905 | +table.diff .diff-addedline { |
|---|
| 2906 | + background-color: #ddffdd; |
|---|
| 2907 | +} |
|---|
| 2908 | +table.diff .diff-addedline ins { |
|---|
| 2909 | + background-color: #99ff99; |
|---|
| 2910 | +} |
|---|
| 2911 | Index: wp-admin/page.php |
|---|
| 2912 | =================================================================== |
|---|
| 2913 | --- wp-admin/page.php (revision 7746) |
|---|
| 2914 | +++ wp-admin/page.php (working copy) |
|---|
| 2915 | @@ -70,8 +70,8 @@ |
|---|
| 2916 | |
|---|
| 2917 | if ( empty($post->ID) ) wp_die( __("You attempted to edit a page that doesn't exist. Perhaps it was deleted?") ); |
|---|
| 2918 | |
|---|
| 2919 | - if ( 'post' == $post->post_type ) { |
|---|
| 2920 | - wp_redirect("post.php?action=edit&post=$post_ID"); |
|---|
| 2921 | + if ( 'page' != $post->post_type ) { |
|---|
| 2922 | + wp_redirect( get_edit_post_link( $post_ID, 'url' ) ); |
|---|
| 2923 | exit(); |
|---|
| 2924 | } |
|---|
| 2925 | |
|---|