array('post' => 'unlimited', 'page' => 'unlimited', 'all' => 'unlimited'), 'revision-range' => '2..5,10,20,50,100' ); function __construct($plugin) { //Set the directory of the plugin: $this->basename = $plugin; $this->folder = dirname($plugin); // Load options - Must be done on inclusion as they're needed by plugins_loaded $this->load_options(); add_action('plugins_loaded', array(&$this, 'define_WP_POST_REVISIONS')); if ( ! is_admin() ) return; //Register general hooks. add_action('init', array(&$this, 'load_translations')); // Needs to be done before admin_menu. add_action('admin_menu', array(&$this, 'admin_menu')); add_action('admin_init', array(&$this, 'admin_init')); } function load_translations() { //Load any translations. load_plugin_textdomain( 'revision-control', false, $this->folder . '/langs/'); } function admin_init() { // Register post/page hook: foreach ( array('load-post-new.php', 'load-post.php', 'load-page-new.php', 'load-page.php') as $page ) add_action($page, array(&$this, 'meta_box')); wp_register_script('revision-control', plugins_url( $this->folder . '/revision-control.js' ), array('jquery', 'wp-ajax-response'), $this->version . time()); wp_register_style('revision-control', plugins_url( $this->folder . '/revision-control.css' ), array(), $this->version . time()); wp_localize_script('revision-control', 'RevisionControl', array( 'deleterevisions' => __('Are you sure you wish to delete the selected Revisions?', 'revision-control'), 'unlockrevision' => __('Warning: Unlocking this post will cause the latest revision to be published!\nContinue?', 'revision-control'), 'selectbothradio' => __('Please select a Left and Right revision to compare', 'revision-control'), 'revisioncompare' => __('Revision Compare', 'revision-control') ) ); // Add post handlers. add_action('admin_post_revision-control-delete', array('Plugin_Revision_Control_Ajax', 'delete_revisions')); add_action('admin_post_revision-control-options', array('Plugin_Revision_Control_Ajax', 'save_options')); add_action('admin_post_revision-control-revision-compare', array('Plugin_Revision_Control_UI', 'compare_revisions_iframe')); add_action('save_post', array(&$this, 'save_post'), 10, 2); // Version the terms. add_action('_wp_put_post_revision', array(&$this, 'version_terms') ); //Delete the terms add_action('wp_delete_post_revision', array(&$this, 'delete_terms'), 10, 2 ); // Version the postmeta add_action('_wp_put_post_revision', array(&$this, 'version_postmeta') ); // Postmeta deletion is handled by core. } function admin_menu() { add_options_page( __('Revision Control', 'revision-control'), __('Revisions', 'revision-control'), 'manage_options', 'revision-control', array('Plugin_Revision_Control_UI', 'admin_page')); } function meta_box() { foreach ( get_post_types() as $type ) { if ( post_type_supports($type, 'revisions') ) { remove_meta_box('revisionsdiv', $type, 'normal'); add_meta_box('revisionsdiv', __('Post Revisions'), array('Plugin_Revision_Control_UI', 'revisions_meta_box'), $type, 'normal'); } } //enqueue that Stylin' script! wp_enqueue_script('revision-control'); wp_enqueue_style('revision-control'); add_thickbox(); } function save_post($id, $post) { $new = isset($_POST['limit_revisions']) ? stripslashes($_POST['limit_revisions']) : false; $old = isset($_POST['limit_revisions_before']) ? stripslashes_deep($_POST['limit_revisions_before']) : false; $id = 'revision' == $post->post_type ? $post->post_parent : $post->ID; if ( false !== $new ) $this->delete_old_revisions($id, $new); if ( false === $new || false === $old || $new == $old) return; update_metadata('post', $id, '_revision-control', $new, $old); } function define_WP_POST_REVISIONS() { if ( defined('WP_POST_REVISIONS') ) { $this->define_failure = true; // This.. Is defineing failure.. as true! return; } $current_post = $this->get_current_post(); if ( !empty($_REQUEST['limit_revisions']) ) { //Handle it when updating a post. if ( ! $default = $this->option($current_post->post_type, 'per-type') ) $default = $this->option('all', 'per-type'); $post_specific = array(stripslashes($_REQUEST['limit_revisions'])); } else if ( $current_post ) { // Good, we've got a post so can base it off the post_type if ( ! $default = $this->option($current_post->post_type, 'per-type') ) $default = $this->option('all', 'per-type'); // Check to see if those post has a custom Revisions value: $post_specific = get_metadata('post', $current_post->ID, '_revision-control', true); if ( '' == $post_specific ) $post_specific = false; else if ( ! is_array($post_specific) ) $post_specific = Plugin_Revision_Control_Compat::postmeta($post_specific, $current_post); } else { // Guess based on the current page. global $pagenow; if ( !empty($_REQUEST['post_type']) ) $post_type = stripslashes($_REQUEST['post_type']); else if ( 'page.php' == $pagenow || 'page-new.php' == $pagenow) $post_type = 'page'; else if ( 'post.php' == $pagenow || 'post-new.php' == $pagenow) $post_type = 'post'; else $post_type = ''; if ( empty($post_type) ) return; //Not needed. if ( ! $default = $this->option($post_type, 'per-type') ) $default = $this->option('all', 'per-type'); } // Ok, Lets define it. $define_to = isset($post_specific[0]) && '' != $post_specific[0] ? $post_specific[0] : $default; switch ( $define_to ) { case 'unlimited': define('WP_POST_REVISIONS', true); break; case 'never': define('WP_POST_REVISIONS', 0); break; case 'defaults': define('WP_POST_REVISIONS', $default); break; default: if ( is_numeric($define_to) ) define('WP_POST_REVISIONS', (int)$define_to); else define('WP_POST_REVISIONS', true); // All else fails, Its this. break; } } function delete_old_revisions($id, $new) { $items = get_posts( array('post_type' => 'revision', 'numberposts' => 1000, 'post_parent' => $id, 'post_status' => 'inherit', 'order' => 'ASC', 'orderby' => 'ID') ); if ( 'defaults' == $new ) { $post = get_post($id); if ( false === $default = $this->option($post->post_type, 'per-type') ) $default = $this->option('all', 'per-type'); $new = $default; } if ( ! is_numeric($new) ) { switch ( $new ) { case 'unlimited': $keep = count($items); break; case 'never': $keep = 0; break; } } else { $keep = $new; } while ( count($items) > $keep ) { $item = array_shift($items); wp_delete_post_revision($item->ID); } } function get_current_post() { foreach ( array( 'post_id', 'post_ID', 'post' ) as $field ) if ( isset( $_REQUEST[ $field ] ) ) return get_post(absint($_REQUEST[ $field ])); if ( isset($_REQUEST['revision']) ) if ( $post = get_post( $id = absint($_REQUEST['revision']) ) ) return get_post($post->post_parent); return false; } function version_terms($revision_id) { // Attach all the terms from the parent to the revision. if ( ! $rev = get_post($revision_id) ) return; if ( ! $post = get_post($rev->post_parent) ) return; // Only worry about taxonomies which are specifically linked. foreach ( get_object_taxonomies($post->post_type) as $taxonomy ) { $_terms = wp_get_object_terms($post->ID, $taxonomy); $terms = array(); foreach ( $_terms as $t ) $terms[] = (int)$t->term_id; if ( ! empty($terms) ) wp_set_object_terms($revision_id, $terms, $taxonomy); } } function delete_terms($revision_id, $rev) { if ( ! $post = get_post($rev->post_parent) ) return; // Delete the parent posts taxonomies from the revision. wp_delete_object_term_relationships($revision_id, get_object_taxonomies($post->post_type) ); } function version_postmeta($revision_id) { // Attach all the terms from the parent to the revision. if ( ! $rev = get_post($revision_id) ) return; if ( ! $post = get_post($rev->post_parent) ) return; // Only worry about taxonomies which are specifically linked. } function sort_revisions_by_time($a, $b) { return strtotime($a->post_modified_gmt) < strtotime($b->post_modified_gmt); } function load_options() { $original = $options = get_option('revision-control', array()); $options = Plugin_Revision_Control_Compat::options($options); // Lets upgrade the options.. if ( $options != $original ) // Update it if an upgrade has taken place. update_option('revision-control', $options); $this->options = array_merge($this->options, $options); // Some default options may be set here, unless the user modifies them } function option($key, $bucket = false, $default = false ) { if ( $bucket ) return isset($this->options[$bucket][$key]) ? $this->options[$bucket][$key] : $default; else return isset($this->options[$key]) ? $this->options[$key] : $default; } function set_option($key, $value, $bucket = false) { if ( $bucket ) $this->options[$bucket][$key] = $value; else $this->options[$key] = $value; update_option('revision-control', $this->options); } function get_revision_limit_select_items($current = false) { $items = array( 'defaults' => __('Default Revision Settings', 'revision-control'), 'unlimited' => __('Unlimited number of Revisions', 'revision-control'), 'never' => __('Do not store Revisions', 'revision-control') ); $values = $this->option('revision-range', ''); $values = explode(',', $values); foreach ( $values as $val ) { $val = trim($val); if ( preg_match('|^(\d+)\.\.(\d+)$|', $val, $matches) ) { foreach ( range( (int)$matches[1], (int)$matches[2]) as $num ) $items[ $num ] = sprintf( _n( 'Maximum %s Revision stored', 'Maximum %s Revisions stored', $num, 'revision-control' ), number_format_i18n($num) ); } else if ( is_numeric($val) ) { $num = (int)$val; $items[ $num ] = sprintf( _n( 'Maximum %s Revision stored', 'Maximum %s Revisions stored', $num, 'revision-control' ), number_format_i18n($num) ); } } if ( false != $current && is_numeric($current) && !isset($items[ $current ]) ) // Support for when the range changes and the global/per-post has changed since. $items[ $current ] = sprintf( _n( 'Maximum %s Revision stored', 'Maximum %s Revisions stored', $current, 'revision-control' ), number_format_i18n($current) ); return $items; } } class Plugin_Revision_Control_Compat { function postmeta($meta, $post) { if ( is_array($meta) ) return $meta; if ( ! is_numeric($meta) ) { $_meta = array($meta); } else { $_meta = array( (int) $meta ); if ( 1 === $_meta[0] ) $_meta[0] = 'unlimited'; else if ( 0 === $meta[0] ) $_meta[0] = 'never'; } if ( $_meta != $meta ) update_metadata('post', $post->ID, '_revision-control', $_meta, $meta); return $_meta; } function options($options) { $_options = $options; if ( ! is_array($options) ) { // Upgrade from 1.0 to 1.1 $options = array( 'post' => $options, 'page' => $options ); } if ( isset($options['post']) ) { // Upgrade from 1.1 to 2.0 $options['per-type'] = array( 'post' => $options['post'], 'page' => $options['page'] ); unset($options['post'], $options['page']); // The fun part, Move from (bool) & (int) to (string) and (int). Easier to seperate with is_numeric that way. foreach ( $options['per-type'] as $type => $value ) { if ( true === $value ) $options['per-type'][$type] = 'unlimited'; elseif ( 0 === $value ) $options['per-type'][$type] = 'never'; elseif ( is_numeric($value) && (int)$value > 0 ) $options['per-type'][$type] = (int)$options['per-type'][$type]; else $options['per-type'][$type] = 'unlimited'; } } return $options; } } class Plugin_Revision_Control_Ajax { function delete_revisions() { //Add nonce check check_admin_referer('revision-control-delete'); if ( empty($_POST['revisions']) ) { $x = new WP_AJAX_Response(); $x->add( array('data' => -1) ); $x->send(); return; } $revisions = stripslashes($_POST['revisions']); $revisions = explode(',', $revisions); $revisions = array_map('intval', $revisions); $deleted = array(); foreach ( $revisions as $revision_id ) { $revision = get_post($revision_id); if ( wp_is_post_revision($revision) && !wp_is_post_autosave($revision) && current_user_can('delete_post', $revision->post_parent) ) if ( wp_delete_post_revision($revision_id) ) $deleted[] = $revision_id; } $x = new WP_AJAX_Response(); $x->add( array('data' => 1, 'supplemental' => array('revisions' => implode(',', $deleted)) ) ); $x->send(); } function save_options() { global $revision_control; check_Admin_referer('revision-control-options'); $data = stripslashes_deep($_POST['options']); foreach ( $data as $option => $val ) { if ( is_string($val) ) // Option is the keyname $revision_control->set_option($option, $val); elseif ( is_array($val) ) // Option is the bucket, key => val are the options in the group. foreach ( $val as $subkey => $subval ) $revision_control->set_option($subkey, $subval, $option); } wp_safe_redirect( add_query_arg('updated', 'true', wp_get_referer() ) ); } } class Plugin_Revision_Control_UI { function compare_revisions_iframe() { //add_action('admin_init', 'register_admin_colors', 1); set_current_screen('revision-edit'); $left = isset($_GET['left']) ? absint($_GET['left']) : false; $right = isset($_GET['right']) ? absint($_GET['right']) : false; if ( !$left_revision = get_post( $left ) ) break; if ( !$right_revision = get_post( $right ) ) break; if ( !current_user_can( 'read_post', $left_revision->ID ) || !current_user_can( 'read_post', $right_revision->ID ) ) break; // Don't allow reverse diffs? if ( strtotime($right_revision->post_modified_gmt) < strtotime($left_revision->post_modified_gmt) ) { //$redirect = add_query_arg( array( 'left' => $right, 'right' => $left ) ); // Switch-a-roo $temp_revision = $left_revision; $left_revision = $right_revision; $right_revision = $temp_revision; unset($temp_revision); } global $post; if ( $left_revision->ID == $right_revision->post_parent ) // right is a revision of left $post = $left_revision; elseif ( $left_revision->post_parent == $right_revision->ID ) // left is a revision of right $post = $right_revision; elseif ( $left_revision->post_parent == $right_revision->post_parent ) // both are revisions of common parent $post = get_post( $left_revision->post_parent ); else wp_die( __('Sorry, But you cant compare unrelated Revisions.', 'revision-control') ); // Don't diff two unrelated revisions if ( // They're the same $left_revision->ID == $right_revision->ID || // Neither is a revision ( !wp_get_post_revision( $left_revision->ID ) && !wp_get_post_revision( $right_revision->ID ) ) ) wp_die( __('Sorry, But you cant compare a Revision to itself.', 'revision-control') ); $title = sprintf( __( 'Compare Revisions of “%1$s”', 'revision-control' ), get_the_title() ); $left = $left_revision->ID; $right = $right_revision->ID; $GLOBALS['hook_suffix'] = 'revision-control'; wp_enqueue_style('revision-control'); iframe_header(); ?>

post_type) as $taxonomy ) { $t = get_taxonomy($taxonomy); $fields[$taxonomy] = $t->label; $left_terms = $right_terms = array(); foreach ( wp_get_object_terms($left_revision->ID, $taxonomy ) as $term ) $left_terms[] = $term->name; foreach ( wp_get_object_terms($right_revision->ID, $taxonomy ) as $term ) $right_terms[] = $term->name; $left_revision->$taxonomy = ( empty($left_terms) ? '' : "* " ) . join("\n* ", $left_terms); $right_revision->$taxonomy = ( empty($right_terms) ? '' : "* " ) . join("\n* ", $right_terms); } $fields['postmeta'] = __('Post Meta', 'revision-control'); $left_revision->postmeta = $right_revision->postmeta = array(); foreach ( (array)has_meta($right_revision->ID) as $meta ) { if ( '_' == $meta['meta_key'][0] ) continue; $right_revision->postmeta[] = $meta['meta_key'] . ': ' . $meta['meta_value']; $left_val = get_post_meta('post', $left_revision->ID, $meta['meta_key'], true); if ( !empty( $left_val ) ) $left_revision->postmeta[] = $meta['meta_key'] . ': ' . $left_val; } $right_revision->postmeta = implode("\n", $right_revision->postmeta); $left_revision->postmeta = implode("\n", $left_revision->postmeta); $identical = true; foreach ( $fields as $field => $field_title ) : if ( !$content = wp_text_diff( $left_revision->$field, $right_revision->$field ) ) continue; // There is no difference between left and right $identical = false; ?>

Please Note: at present, Although Taxonomies (Tags / Categories / Custom Taxonomies) are stored with the revisions, Restoring a Revision will not restore the taxonomies at present.', 'revision-control'); ?>


ID ) ) $revisions = array(); if ( !empty($post) && !empty($post->ID) ) $revisions = $revisions + array($post); foreach ( $revisions as $key => $revision ) { if ( !current_user_can( 'read_post', $revision->ID ) ) unset($revisions[$key]); } usort($revisions, array('Plugin_Revision_Control', 'sort_revisions_by_time')); ?> ID ) $can_edit_post = true; else $can_edit_post = current_user_can( 'edit_post', $post->ID ); //$locked_revision = false; if ( empty($revisions) ) { echo "\n"; echo "\t\n"; echo "\n"; } foreach ( $revisions as $revision ) { $date = wp_post_revision_title( $revision, false ); $name = get_the_author_meta( 'display_name', $revision->post_author ); $revision_is_current = $post->ID == $revision->ID; /*$revision_is_locked = $revision->ID == $locked_revision;*/ $class = strpos($class, 'alternate') !== false ? '' : "alternate"; $class .= ' revision-' . $revision->ID; if ( $revision_is_current ) $class .= ' current-revision'; /*if ( $revision_is_locked ) $class .= ' locked-revision';*/ $actions = array(); /*if ( !$revision_is_locked ) $actions[] = '' . __('Lock', 'revision-control') . ''; else $actions[] = '' . __('Unlock', 'revision-control') . '';*/ if ( ! $revision_is_current && !wp_is_post_autosave($revision) && $can_edit_post ) { $actions[] = 'ID|$revision->ID" ) . '">' . __( 'Restore', 'revision-control' ) . ''; //$actions[] = '' . __( 'Delete', 'revision-control' ) . ''; } $deletedisabled = ( $revision_is_current || wp_is_post_autosave($revision) || ! $can_edit_post ) ? 'disabled="disabled"' : ''; //$revision_is_locked || ($revision_is_current && false === $locked_revision) $lefthidden = $revision == end($revisions) ? ' style="visibility: hidden" ' : ''; $righthidden = $revision == $revisions[0] ? ' style="visibility: hidden" ' : ''; echo "\n"; echo "\t\n"; echo "\t\n"; echo "\t\n"; echo "\t\n"; echo "\n"; } ?>
\n"; $p_obj = get_post_type_object($post->post_type); $obj_name = $p_obj->label; printf(_x('Revisions are currently enabled for %s, However there are no current Autosaves or Revisions created.
They\'ll be listed here once you Save. Happy Writing!', '1: the Post_Type - Posts, Pages, etc. (plural always)', 'revision-control'), $obj_name); echo "
$date$name
define_failure ) { _e('Error: WP_POST_REVISIONS is defined in your wp-config.php. Revision Control cannot operate.', 'revision-control'); } else { $_current = $post_specific = get_metadata('post', $post->ID, '_revision-control', true); if ( '' == $post_specific ) $post_specific = array('defaults'); else if ( ! is_array($post_specific) ) $post_specific = Plugin_Revision_Control_Compat::postmeta($post_specific, $post); $post_specific = $post_specific[0]; $_current_display = $post_specific; if ( 'defaults' == $_current_display ) $_current_display = $revision_control->option($post->post_type, 'per-type'); ?>

"; screen_icon('options-general'); echo '

' . __('Revision Control Options', 'revision-control') . '

'; echo '

' . __('Default revision status for Post Types', 'revision-control') . '

'; if ( function_exists('post_type_supports') ) { $types = array(); $_types = get_post_types(); foreach ( $_types as $type ) { if ( post_type_supports($type, 'revisions') ) $types[] = $type; } } else { $types = array('post', 'page'); } echo '
'; wp_nonce_field('revision-control-options'); echo ''; echo ''; echo ''; echo '
' . __('Default Revision Status', 'revision-control') . ' '; foreach ( $types as $post_type ) { $post_type_name = $post_type; if ( !in_array($post_type, array('post', 'page')) && function_exists('get_post_type_object') ) { $pt = get_post_type_object($post_type); $post_type_name = $pt->label; unset($pt); } else { if ( 'post' == $post_type ) $post_type_name = _n('Post', 'Posts', 5, 'revision-control'); elseif ( 'page' == $post_type ) $post_type_name = _n('Page', 'Pages', 5, 'revision-control'); } echo ''; echo ''; } echo '
'; echo '

' . __('Note: This field is special. It controls what appears in the Revision Options <select> fields.
The basic syntax of this is simple, fields are seperated by comma\'s.
A field may either be a number, OR a range.
For example: 1,5 displays 1 Revision, and 5 Revisions. 1..5 on the other hand, will display 1.. 2.. 3.. 4.. 5.. Revisions.
If in doubt, Leave this field alone.
', 'revision-control') . '
'; submit_button( __('Save Changes', 'revision-control') ); echo '
'; echo '
'; } }