diff --git a/admin.php b/admin.php index 27095728..dacf0709 100644 --- a/admin.php +++ b/admin.php @@ -50,6 +50,9 @@ function __construct( &$instance = null) { //media filters add_action( 'posts_where', array( &$this, 'filter_from_media' ) ); + + //cleanup + add_action( 'delete_post', array( &$this, 'delete_attachments_with_document'), 10, 1 ); } @@ -225,11 +228,11 @@ function document_metabox($post) {?> get_document_lock( $post ) ) { ?> -
- - If you believe this is in error you can override the lock, but their changes will be lost.', 'wp-document-revisions'); ?> - -
+
+ + If you believe this is in error you can override the lock, but their changes will be lost.', 'wp-document-revisions'); ?> + +
Upload New Version
- + + /** + * Callback to create the upload location settings field + * @since 0.5 + */ + function upload_location_cb() { ?> + wp_content/uploads folder, but it may be moved to a folder outside of the htdocs or public_html folder for added security.', 'wp-document-revisions' ); ?> - get_file_type( $post ); - - //begin output buffer so the javascript can be returned as a string, rather than output directly to the browser - ob_start(); - - ?>get_file_type( $post ); + + //begin output buffer so the javascript can be returned as a string, rather than output directly to the browser + ob_start(); + + ?>verify_post_type( ) ) return $meta; @@ -429,10 +432,10 @@ function media_meta_hack( $meta ) { $meta .= $this->post_upload_js( $latest->ID ); return $meta; - - } - - /** + + } + + /** * Hook to follow file uploads to automate attaching the document to the post * @param string $filter whatever we really should be filtering * @returns string the same stuff they gave us, like we were never here @@ -457,8 +460,8 @@ function post_upload_handler( $filter ) { //should probably give this back... return $filter; - } - + } + /** * Retrieves the most recent file attached to a post * @param int $post_id the parent post @@ -658,20 +661,9 @@ function workflow_state_save( $post_id ) { //all's good, let's save wp_set_post_terms( $post_id, array( $_POST['workflow_state'] ), 'workflow_state' ); - } - - /** - * Filters permalink displayed on edit screen in the event that there is no attachment yet uploaded - * @param string $html original HTML - * @param int $id Post ID - * @rerurns string modified HTML - * @since 0.5 - */ - function sample_permalink_filter($html, $id ) { - - return $html; + do_action( 'change_document_workflow_state', $post_id, $_POST['workflow_state'] ); - } + } /** * Slightly modified document author metabox because the current one is ugly @@ -760,6 +752,21 @@ function revision_filter( $id ) { wp_delete_post( $id, true ); } + + /** + * Deletes all attachments associated with a document or revision + * @since 1.0 + * @param int $postID the id of the deleted post + */ + function delete_attachments_with_document( $postID ) { + + if ( !$this->verify_post_type( $postID ) ) + return; + + if ( is_numeric( $post->post_content ) && get_post( $post->post_content ) ) + wp_delete_attachment( $post->post_content, false ); + + } } ?> \ No newline at end of file diff --git a/readme.txt b/readme.txt index fa249125..3649153f 100644 --- a/readme.txt +++ b/readme.txt @@ -3,37 +3,58 @@ Contributors: benbalter Donate link: http://ben.balter.com/ Tags: documents, uploads, attachments, document management, enterprise, version control, revisions, collaboration, journalism, government, files, revision log, document management Requires at least: 3.2 -Tested up to: 3.2 +Tested up to: 3.3 Stable tag: trunk -WordPress Document Revisions – a workflow management and version control system for WordPress +WordPress Document Revisions – a document management and version control plugin that allows teams of any size to collaboratively edit files == Description == -WordPress Document Management – a workflow management and version control system for WordPress building on its existing core competencies. By treating documents as a custom post type, users can leverage the power of WordPress’s extensive attachment, revision, taxonomy, and URL rewriting functionalities. Document permalinks can be routed through the traditional rewrite structure such that the latest revision of a file always remains at a static, authenticated URL, and users can toggle the visibility of documents (both internally and externally) as they currently do with post statuses and permissions. Similarly, file locking can extend WordPress’s autosave functionality (as a ping), revision logs can extend WordPress’s existing revision relationship and can be outputted as a traditional RSS feed, etc. - -[Read More in my original post "When all you have is a pair of bolt cutters..."](http://ben.balter.com/2011/04/04/when-all-you-have-is-a-pair-of-bolt-cutters/) - **Google Summer of Code 2011 project. Still under development. Not for production. This will break.** Please feel free to download and kick the tires. Community feedback is greatly appreciated, but please note that this project is still under development, and is not quite ready for production environments. -**Features** +Read More in my original post ["When all you have is a pair of bolt cutters…](http://ben.balter.com/2011/04/04/when-all-you-have-is-a-pair-of-bolt-cutters/) + +-------------------------------- + +[WP Document Revisions](http://wordpress.org/extend/plugins/wp-document-revisions/) is a [document management](http://en.wikipedia.org/wiki/Document_management_system) and [version control](http://en.wikipedia.org/wiki/Revision_control) plugin. Built for time-sensitive and mission-critical projects, teams can collaboratively edit files of any format -- text documents, spreadsheets, images, sheet music... anything -- all the while, seamlessly tracking the document's progress as it moves through your organization's existing workflow. + +**Powerful Collaboration Tools ***With great power does not have to come great complexity. *Based on a simple philosophy of putting powerful but intuitive tools in the hands of managers and content creators, WP Document Revisions leverages many of the essential WordPress features that, for more than eight years, have been tested and proven across countless industries -- posts, attachments, revisions, taxonomies, authentication, and permalinks -- to make collaborating on the creation and publication of documents a natural endeavor. Think of it as an [open-source and more intuitive version](http://ben.balter.com/2011/04/04/when-all-you-have-is-a-pair-of-bolt-cutters/) of the popular Microsoft collaboration suite, [Sharepoint.](http://sharepoint.microsoft.com/en-us/Pages/default.aspx) + +**Document History **At each step of the authoring process, WP Document Revisions gives you an instant snapshot of your team's progress and the document's history. It even gives you the option to revert back to a previous revision -- so don't fret if you make a mistake -- or receive updates on changes to the document right in your favorite feed reader. + +**Access Control **Each document is given a persistent URL (e.g., yourcompany.com/documents/2011/08/TPS-Report.doc) which can be private (securely delivered only to members of your organization), password protected (available only to those you select), or public (published and hosted for the world to see). If you catch a typo and upload a new version, that URL will continue to point to the latest version, regardless of how many changes you make. -* Support for any file type (docs, spreadsheets, images, PDFs, etc.) -* Stores unlimited revisions with a revision log message for each -* Each file gets a permanent URL that always points to the latest version of the file -* Each revision gets its own unique url (e.g., my-document-revision-3.doc) -* Toggle documents between public, private, and password protected +**Enterprise Security** Worried about storing propriety or sensitive information? WP Document Revisions was built from the first line of code with government- and enterprise-grade security in mind. Each file is masked behind an anonymous 128-bit [MD5 hash](http://en.wikipedia.org/wiki/MD5) as soon as it touches the server, and requests for files are transparently routed through WordPress's time-tested URL rewriting, authentication, and permission systems (which can even [integrate with existing enterprise active directory](http://wordpress.org/extend/plugins/active-directory-integration/) or [LDAP servers](http://wordpress.org/extend/plugins/simple-ldap-login/)). Need more security? WP Document Revisions allows you to store documents in a folder above the `htdocs` or `public_html` [web root](http://httpd.apache.org/docs/2.0/mod/core.html#documentroot), further ensuring that only those you authorize have access to your work. + +**Customization** WP Document Revisions recognizes that no two teams are identical, and as a result, molds to your firm's needs, not the other way around. Need to track additional information associated with a document? Departments, editors, issues, sections, even arbitrary key-value pairs -- whatever you can throw at it, it can handle. Development and customization costs are further minimized by its extensive plugin API, and the [WP Document Revisions Custom Taxonomy Generator](#) makes it easy for even the uninitiated to add custom fields and taxonomies to documents. Simply put, virtually every aspect of the plugin's functionality from workflow states to user-level permissions can be fully customized to your team's unique needs. + +**The Vitals:** + +* Support for any file type (docs, spreadsheets, images, PDFs -- anything!) +* Securely stores unlimited revisions of your business's essential files +* Provides a full file history in the form of a revision log, accessible via RSS +* Helps you track and organize documents as they move through your organization's existing workflow +* Each file gets a permanent, authenticated URL that always points to the latest version +* Each revision gets its own unique url (e.g.,TPS-Report-revision-3.doc) accessible only to those you deem +* Files are intuitively checked out and locked to prevent revisions from colliding +* Toggle documents between public, private, and password protected with a single mouse click * Secure: filenames are hashed on upload and files are only accessible through WordPress's proven authentication system -* Can move document upload folder to location outside of web root to further secure files -* Helps you track and organize documents as they move through your organization's workflow -* RSS Feeds of revisions +* Can move document upload folder to location outside of web root to further ensure government- and enterprise-grade security +* Easily translated to your local language + +Want to give it a try? Simply [download WP Document Revisions](http://wordpress.org/extend/plugins/wp-document-revisions/) from the WordPress Plugin Repository. + +*WP Document Revisions was **developed by a [law student and a business student](http://ben.balter.com) **with a [grant from Google](http://code.google.com/soc/),** and in close coordination with and under the watchful eye of WordPress.org's lead developers.*** (Neither relationship should imply a formal endorsement.) ** **Known Issues** * See the [development backlog](http://gsoc.trac.wordpress.org/query?status=accepted&status=assigned&status=new&status=reopened&component=Document+Revisions&col=id&col=summary&col=status&col=type&col=priority&col=milestone&col=component&order=priority) for a list of known issues +== Screenshots == +1. A typical WP Document Revisions edit document screen. + == Installation == 1. Download and Install diff --git a/screenshot-1.png b/screenshot-1.png new file mode 100644 index 00000000..3b5eba72 Binary files /dev/null and b/screenshot-1.png differ diff --git a/wp-document-revisions.dev.js b/wp-document-revisions.dev.js index abc09722..6259c4b1 100644 --- a/wp-document-revisions.dev.js +++ b/wp-document-revisions.dev.js @@ -2,11 +2,11 @@ jQuery(document).ready( function($) { //Revision restore confirmation $('.revision').click(function(event){ - event.preventDefault(); - if (confirm( wp_document_revisions.restoreConfirmation ) ) - window.location.href = jQuery(this).attr('href'); + event.preventDefault(); + if (confirm( wp_document_revisions.restoreConfirmation ) ) + window.location.href = jQuery(this).attr('href'); }); - + //lock override toggle $('#override_link').click( function() { @@ -22,9 +22,9 @@ jQuery(document).ready( function($) { } else { alert( wp_document_revisions.lockError ); } - } + } ); - + }); //HTML5 Lock Override Notifications permission check on document download @@ -35,22 +35,22 @@ jQuery(document).ready( function($) { //HTML5 Lock Override Notifications function lock_override_notice( notice ) { - if ( window.webkitNotifications.checkPermission() > 0 ) { - window.webkitNotifications.RequestPermission( lock_override_notice ); - } else { - window.webkitNotifications.createNotification( - 'icon.png', wp_document_revisions.lostLockNoticeTitle, notice ).show(); - } + if ( window.webkitNotifications.checkPermission() > 0 ) { + window.webkitNotifications.RequestPermission( lock_override_notice ); + } else { + window.webkitNotifications.createNotification( + 'icon.png', wp_document_revisions.lostLockNoticeTitle, notice ).show(); + } } //disbale the update button until a doc has been uploaded - if ( adminpage && adminpage == 'post-php' && typenow && typenow == 'document' ) { - - //set a flag to let us know if there's been an upload yet - hasUpload = false; - - //disable the button (from autosave.js) - jQuery(':button, :submit', '#submitpost').prop('disabled', true); + if ( adminpage && ( adminpage == 'post-php' || adminpage == 'post-new-php' ) && typenow && typenow == 'document' ) { + + //set a flag to let us know if there's been an upload yet + hasUpload = false; + + //disable the button (from autosave.js) + jQuery(':button, :submit', '#submitpost').prop('disabled', true); //rename the function the autosave.js uses to enable the button to check our flag wp_document_revisions_autosave_enable_buttons = autosave_enable_buttons; @@ -60,7 +60,7 @@ jQuery(document).ready( function($) { //trigger a post-autosave event to check the lock $(document).trigger('autosaveComplete'); - + if ( hasUpload ) wp_document_revisions_autosave_enable_buttons(); } @@ -73,33 +73,33 @@ jQuery(document).ready( function($) { //it will be new if lock-notice is still present, also prevents notice from firing on initial load if document is locked if ( $('#autosave-alert').length > 0 && $('#lock-notice').length > 0 && $('#lock-notice').is(":visible") ) { - wp_document_revisions.lostLockNotice = wp_document_revisions.lostLockNotice.replace('%s', $('#title').val() ); - - if ( window.webkitNotifications ) { - //browser supports html5 Notifications - lock_override_notice( wp_document_revisions.lostLockNotice ); - } else { - //browser does not support lock override notice, send old school alert - alert( convertEntities( wp_document_revisions.lostLockNotice ) ); - } + wp_document_revisions.lostLockNotice = wp_document_revisions.lostLockNotice.replace('%s', $('#title').val() ); + + if ( window.webkitNotifications ) { + //browser supports html5 Notifications + lock_override_notice( wp_document_revisions.lostLockNotice ); + } else { + //browser does not support lock override notice, send old school alert + alert( convertEntities( wp_document_revisions.lostLockNotice ) ); + } //reload the page to lock them out and prevent duplicate alerts - location.reload(true); + location.reload(true); } }); //if post status is changed, enable the submit button so the change can be saved $('#misc-publishing-actions a').click( function(){ - - //re-enabled the submit button + + //re-enabled the submit button $(':button, :submit', '#submitpost').removeAttr('disabled'); }); //if any metabox is changed, allow submission - $('.postbox input, .postbox select, .postbox text area').change( function() { - + $('input, select, textarea').live('change', function() { + //re-enabled the submit button $(':button, :submit', '#submitpost').removeAttr('disabled'); @@ -134,13 +134,10 @@ jQuery(document).ready( function($) { //notify user of success by adding the post upload notice before the #post div //to ensure we get the user's attention, blink once (via fade in, fade out, fade in again). - win.jQuery('#post').before( convertEntities( wp_document_revisions.postUploadNotice ) ).prev().fadeIn().fadeOut().fadeIn(); + win.jQuery('#post').before( wp_document_revisions.postUploadNotice ).prev().fadeIn().fadeOut().fadeIn(); //If they already have a permalink, update it with the current extension in case it changed - //otherwise, tell WP that we're ready for it to generate a permalink for the first time - if ( win.jQuery('#sample-permalink').length == 0 ) { - win.autosave_update_slug( post_id ); - } else { + if ( win.jQuery('#sample-permalink').length != 0 ) { win.jQuery('#sample-permalink').html( win.jQuery('#sample-permalink').html().replace(/\<\/span>(\.[a-z0-9]{3,4})?$/i, wp_document_revisions.extension ) ); } diff --git a/wp-document-revisions.js b/wp-document-revisions.js index 6a1eadb8..fb8754d2 100644 --- a/wp-document-revisions.js +++ b/wp-document-revisions.js @@ -1,7 +1,7 @@ jQuery(document).ready(function(a){function b(a){window.webkitNotifications.checkPermission()>0?window.webkitNotifications.RequestPermission(b):window.webkitNotifications.createNotification("icon.png",wp_document_revisions.lostLockNoticeTitle,a).show()}a(".revision").click(function(a){a.preventDefault();if(confirm(wp_document_revisions.restoreConfirmation))window.location.href=jQuery(this).attr("href")});a("#override_link").click(function(){jQuery.post(ajaxurl,{action:"override_lock",post_id:jQuery("#post_ID").val()|| -0},function(c){c?(a("#lock_override").hide(),a(".error").not("#lock-notice").hide(),a("#publish, #add_media, #lock-notice").fadeIn(),autosave()):alert(wp_document_revisions.lockError)})});a("#document a").click(function(){window.webkitNotifications&&window.webkitNotifications.requestPermission()});adminpage&&adminpage=="post-php"&&typenow&&typenow=="document"&&(hasUpload=!1,jQuery(":button, :submit","#submitpost").prop("disabled",!0),wp_document_revisions_autosave_enable_buttons=autosave_enable_buttons, -autosave_enable_buttons=function(){a(document).trigger("autosaveComplete");hasUpload&&wp_document_revisions_autosave_enable_buttons()});a(document).bind("autosaveComplete",function(){if(a("#autosave-alert").length>0&&a("#lock-notice").length>0&&a("#lock-notice").is(":visible"))wp_document_revisions.lostLockNotice=wp_document_revisions.lostLockNotice.replace("%s",a("#title").val()),window.webkitNotifications?b(wp_document_revisions.lostLockNotice):alert(convertEntities(wp_document_revisions.lostLockNotice)), -location.reload(!0)});a("#misc-publishing-actions a").click(function(){a(":button, :submit","#submitpost").removeAttr("disabled")});a(".postbox input, .postbox select, .postbox text area").change(function(){a(":button, :submit","#submitpost").removeAttr("disabled")});a(document).bind("documentUpload",function(){var a=window.dialogArguments||opener||parent||top;if(!a.hasUpload)a.jQuery("#content").val(attachmentID),a.jQuery("#message").hide(),a.jQuery("#revision-summary").show(),a.jQuery(":button, :submit", -"#submitpost").removeAttr("disabled"),a.hasUpload=!0,a.tb_remove(),a.jQuery("#post").before(convertEntities(wp_document_revisions.postUploadNotice)).prev().fadeIn().fadeOut().fadeIn(),a.jQuery("#sample-permalink").length==0?a.autosave_update_slug(post_id):a.jQuery("#sample-permalink").html(a.jQuery("#sample-permalink").html().replace(/\<\/span>(\.[a-z0-9]{3,4})?$/i,wp_document_revisions.extension))});setTimeout("updateTimestamps",3E3)}); +0},function(c){c?(a("#lock_override").hide(),a(".error").not("#lock-notice").hide(),a("#publish, #add_media, #lock-notice").fadeIn(),autosave()):alert(wp_document_revisions.lockError)})});a("#document a").click(function(){window.webkitNotifications&&window.webkitNotifications.requestPermission()});if(adminpage&&(adminpage=="post-php"||adminpage=="post-new-php")&&typenow&&typenow=="document")hasUpload=!1,jQuery(":button, :submit","#submitpost").prop("disabled",!0),wp_document_revisions_autosave_enable_buttons= +autosave_enable_buttons,autosave_enable_buttons=function(){a(document).trigger("autosaveComplete");hasUpload&&wp_document_revisions_autosave_enable_buttons()};a(document).bind("autosaveComplete",function(){if(a("#autosave-alert").length>0&&a("#lock-notice").length>0&&a("#lock-notice").is(":visible"))wp_document_revisions.lostLockNotice=wp_document_revisions.lostLockNotice.replace("%s",a("#title").val()),window.webkitNotifications?b(wp_document_revisions.lostLockNotice):alert(convertEntities(wp_document_revisions.lostLockNotice)), +location.reload(!0)});a("#misc-publishing-actions a").click(function(){a(":button, :submit","#submitpost").removeAttr("disabled")});a("input, select, textarea").live("change",function(){a(":button, :submit","#submitpost").removeAttr("disabled")});a(document).bind("documentUpload",function(){var a=window.dialogArguments||opener||parent||top;if(!a.hasUpload)a.jQuery("#content").val(attachmentID),a.jQuery("#message").hide(),a.jQuery("#revision-summary").show(),a.jQuery(":button, :submit","#submitpost").removeAttr("disabled"), +a.hasUpload=!0,a.tb_remove(),a.jQuery("#post").before(wp_document_revisions.postUploadNotice).prev().fadeIn().fadeOut().fadeIn(),a.jQuery("#sample-permalink").length!=0&&a.jQuery("#sample-permalink").html(a.jQuery("#sample-permalink").html().replace(/\<\/span>(\.[a-z0-9]{3,4})?$/i,wp_document_revisions.extension))});setTimeout("updateTimestamps",3E3)}); function human_time_diff(a,b){d=new Date;b=b||d.getTime()/1E3+parseInt(wp_document_revisions.offset);diff=Math.abs(b-a);if(diff<=3600)return mins=Math.floor(diff/60),mins<=1&&(mins=1),mins==1?wp_document_revisions.minute.replace("%d",mins):wp_document_revisions.minutes.replace("%d",mins);else if(diff<=86400&&diff>3600)return hours=Math.floor(diff/3600),hours<=1&&(hours=1),hours==1?wp_document_revisions.hour.replace("%d",hours):wp_document_revisions.hours.replace("%d",hours);else if(diff>=86400)return days= Math.floor(diff/86400),days<=1&&(days=1),days==1?wp_document_revisions.day.replace("%d",days):wp_document_revisions.days.replace("%d",days)}function updateTimestamps(){jQuery(".timestamp").each(function(){jQuery(this).text(human_time_diff(jQuery(this).attr("id")))})}; \ No newline at end of file diff --git a/wp-document-revisions.php b/wp-document-revisions.php index 3cf270c6..f423e6e1 100644 --- a/wp-document-revisions.php +++ b/wp-document-revisions.php @@ -3,7 +3,7 @@ Plugin Name: WP Document Revisions Plugin URI: http:// Description: Document Revisioning and Version Control for WordPress; GSoC 2011. -Version: 0.5.2 +Version: 0.5.3 Author: Benjamin J. Balter Author URI: http://ben.balter.com License: GPL2 @@ -21,7 +21,7 @@ class Document_Revisions { function __construct() { self::$instance = $this; - + //admin add_action( 'admin_menu', array( &$this, 'admin_init' ) ); @@ -49,7 +49,7 @@ function __construct() { add_filter( 'attachment_link', array( &$this, 'attachment_link_filter'), 10, 2); add_filter( 'wp_handle_upload_prefilter', array(&$this, 'filename_rewrite' ) ); add_filter( 'wp_handle_upload', array( &$this, 'rewrite_file_url' ), 10, 2); - + //locking add_action( 'wp_ajax_override_lock', array( &$this, 'override_lock' ) ); @@ -274,9 +274,9 @@ function inject_rules(){ global $wp_rewrite; $wp_rewrite->add_rewrite_tag( "%document%", '([^.]+)\.[A-Za-z0-9]{3,4}?', 'document=' ); - + } - + /** * Adds document rewrite rules to the rewrite array * @since 0.5 @@ -300,7 +300,7 @@ function revision_rewrite( $rules ) { return $my_rules + $rules; } - + /** * Tell's WP to recognize document query vars * @since 0.5 @@ -312,7 +312,7 @@ function add_query_var( $vars ) { $vars[] = "document"; return $vars; } - + /** * Builds document post type permalink * @param string $link original permalink @@ -324,7 +324,7 @@ function permalink( $link, $post, $leavename, $sample = '' ) { //if this isn't our post type, kick if( !$this->verify_post_type( $post ) ) - return $link; + return $link; //check if it's a revision if ( $post->post_type == 'revision' ) { @@ -336,10 +336,10 @@ function permalink( $link, $post, $leavename, $sample = '' ) { // build documents/yyyy/mm/slug $extension = $this->get_file_type( $post ); - + $timestamp = strtotime($post->post_date); - $link = '/documents/' . date('Y',$timestamp) . '/' . date('m',$timestamp) . '/'; - $link .= ( $leavename ) ? '%postname%' : $post->post_name; + $link = home_url() . '/documents/' . date('Y',$timestamp) . '/' . date('m',$timestamp) . '/'; + $link .= ( $leavename ) ? '%document%' : $post->post_name; $link .= $extension ; $link = apply_filters( 'document_permalink', $link, $post );