mirror of
https://github.com/silverstripe/silverstripe-dms
synced 2024-10-22 14:05:56 +02:00
API-CHANGE: adding document versioning
This commit is contained in:
parent
3643d3f3a6
commit
1bd5d929ef
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
define('DMS_DIR', 'dms');
|
||||
if (!file_exists(BASE_PATH . DIRECTORY_SEPARATOR . DMS_DIR)) user_error("DMS directory named incorrectly. Please install the DMS module into a folder named: ".DMS_DIR);
|
||||
|
||||
Object::add_extension('SiteTree','DMSSiteTreeExtension');
|
||||
Object::add_extension('HtmlEditorField_Toolbar','DocumentHtmlEditorFieldToolbar');
|
||||
@ -7,4 +8,10 @@ CMSMenu::remove_menu_item('DMSDocumentAddController');
|
||||
|
||||
ShortcodeParser::get('default')->register('dms_document_link', array('DMSDocument_Controller', 'dms_link_shortcode_handler'));
|
||||
|
||||
if (!file_exists(BASE_PATH . DIRECTORY_SEPARATOR . DMS_DIR)) user_error("DMS directory named incorrectly. Please install the DMS module into a folder named: ".DMS_DIR);
|
||||
DMSDocument_versions::$enable_versions = true;
|
||||
|
||||
if (DMSDocument_versions::$enable_versions) {
|
||||
//using the same db relations for the versioned documents, as for the actual documents
|
||||
Config::inst()->update('DMSDocument_versions', 'db', DMSDocument::$db);
|
||||
}
|
||||
|
||||
|
@ -365,7 +365,8 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
|
||||
* @return DataList List of Document objects
|
||||
*/
|
||||
function getVersions() {
|
||||
// TODO: Implement getVersions() method.
|
||||
if (!DMSDocument_versions::$enable_versions) user_error("DMSDocument versions are disabled",E_USER_WARNING);
|
||||
return DMSDocument_versions::get_versions($this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -405,10 +406,22 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
|
||||
|
||||
$this->removeAllPages();
|
||||
|
||||
//get rid of any versions have saved for this DMSDocument, too
|
||||
if (DMSDocument_versions::$enable_versions) {
|
||||
$versions = $this->getVersions();
|
||||
if ($versions->Count() > 0) {
|
||||
foreach($versions as $v) {
|
||||
$v->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//delete the dataobject
|
||||
parent::delete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Relate an existing file on the filesystem to the document.
|
||||
* Copies the file to the new destination, as defined in {@link get_DMS_path()}.
|
||||
@ -427,6 +440,12 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
|
||||
|
||||
//copy the file into place
|
||||
$fromPath = BASE_PATH . DIRECTORY_SEPARATOR . $filePath;
|
||||
|
||||
//version the existing file (copy it to a new "very specific" filename
|
||||
if (DMSDocument_versions::$enable_versions) {
|
||||
DMSDocument_versions::create_version($this);
|
||||
}
|
||||
|
||||
copy($fromPath, $toPath); //this will overwrite the existing file (if present)
|
||||
|
||||
//write the filename of the stored document
|
||||
@ -510,6 +529,9 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
|
||||
|
||||
$fields = new FieldList(); //don't use the automatic scaffolding, it is slow and unnecessary here
|
||||
|
||||
$extraTasks = ''; //additional text to inject into the list of tasks at the bottom of a DMSDocument CMSfield
|
||||
$extraFields = FormField::create('Empty');
|
||||
|
||||
//get list of shortcode page relations
|
||||
$relationFinder = new ShortCodeRelationFinder();
|
||||
$relationList = $relationFinder->getList($this->ID);
|
||||
@ -554,7 +576,6 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
|
||||
$gridFieldConfig
|
||||
);
|
||||
|
||||
|
||||
$referencesGrid = GridField::create(
|
||||
'References',
|
||||
_t('DMSDocument.RelatedReferences', 'Related References'),
|
||||
@ -562,13 +583,35 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
|
||||
$gridFieldConfig
|
||||
);
|
||||
|
||||
if (DMSDocument_versions::$enable_versions) {
|
||||
$versionsGridFieldConfig = GridFieldConfig::create()->addComponents(
|
||||
new GridFieldToolbarHeader(),
|
||||
new GridFieldSortableHeader(),
|
||||
new GridFieldDataColumns(),
|
||||
new GridFieldPaginator(30)
|
||||
);
|
||||
$versionsGridFieldConfig->getComponentByType('GridFieldDataColumns')->setDisplayFields(Config::inst()->get('DMSDocument_versions', 'display_fields'))
|
||||
->setFieldCasting(array('LastChanged'=>"Datetime->Ago"))
|
||||
->setFieldFormatting(array('FilenameWithoutID'=>'<a target=\'_blank\' class=\'file-url\' href=\'$Link\'>$FilenameWithoutID</a>'));
|
||||
|
||||
$versionsGrid = GridField::create(
|
||||
'Versions',
|
||||
_t('DMSDocument.Versions', 'Versions'),
|
||||
$this->getVersions(),
|
||||
$versionsGridFieldConfig
|
||||
);
|
||||
$extraTasks .= '<li class="ss-ui-button" data-panel="find-versions">Versions</li>';
|
||||
$extraFields = $versionsGrid->addExtraClass('find-versions');
|
||||
}
|
||||
|
||||
$fields->add(new LiteralField('BottomTaskSelection',
|
||||
'<div id="Actions" class="field actions"><label class="left">Actions</label><ul>'.
|
||||
'<li class="ss-ui-button" data-panel="embargo">Embargo</li>'.
|
||||
'<li class="ss-ui-button" data-panel="expiry">Expiry</li>'.
|
||||
'<li class="ss-ui-button" data-panel="replace">Replace</li>'.
|
||||
'<li class="ss-ui-button" data-panel="find-usage">Find usage</li>'.
|
||||
'<li class="ss-ui-button" data-panel="find-references">Find references</li>'.
|
||||
'<li class="ss-ui-button" data-panel="find-usage">Usage</li>'.
|
||||
'<li class="ss-ui-button" data-panel="find-references">References</li>'.
|
||||
$extraTasks.
|
||||
'</ul></div>'));
|
||||
|
||||
$embargoValue = 'None';
|
||||
@ -599,7 +642,8 @@ class DMSDocument extends DataObject implements DMSDocumentInterface {
|
||||
)->addExtraClass('expiry'),
|
||||
$uploadField->addExtraClass('replace'),
|
||||
$pagesGrid->addExtraClass('find-usage'),
|
||||
$referencesGrid->addExtraClass('find-references')
|
||||
$referencesGrid->addExtraClass('find-references'),
|
||||
$extraFields
|
||||
)->setName("ActionsPanel")->addExtraClass('dmsupload ss-uploadfield'));
|
||||
|
||||
$this->extend('updateCMSFields', $fields);
|
||||
@ -745,9 +789,18 @@ class DMSDocument_Controller extends Controller {
|
||||
*/
|
||||
protected function getDocumentFromID($request) {
|
||||
$doc = null;
|
||||
$id = Convert::raw2sql(intval($request->param('ID')));
|
||||
$doc = DataObject::get_by_id('DMSDocument', $id);
|
||||
$this->extend('updateDocumentFromID', $doc, $request);
|
||||
|
||||
$id = Convert::raw2sql($request->param('ID'));
|
||||
|
||||
if (strpos($id, 'version') === 0) { //versioned document
|
||||
$id = str_replace('version','',$id);
|
||||
$doc = DataObject::get_by_id('DMSDocument_versions', $id);
|
||||
$this->extend('updateVersionFromID', $doc, $request);
|
||||
} else { //normal document
|
||||
$doc = DataObject::get_by_id('DMSDocument', $id);
|
||||
$this->extend('updateDocumentFromID', $doc, $request);
|
||||
}
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
@ -761,22 +814,28 @@ class DMSDocument_Controller extends Controller {
|
||||
$canView = false;
|
||||
|
||||
//Runs through all pages that this page links to and sets canView to true if the user can view ONE of these pages
|
||||
$pages = $doc->Pages();
|
||||
if ($pages->Count() > 0) {
|
||||
foreach($pages as $page) {
|
||||
if ($page->CanView()) {
|
||||
$canView = true; //just one canView is enough to know that we can view the file
|
||||
break;
|
||||
if (method_exists($doc, 'Pages')) {
|
||||
$pages = $doc->Pages();
|
||||
if ($pages->Count() > 0) {
|
||||
foreach($pages as $page) {
|
||||
if ($page->CanView()) {
|
||||
$canView = true; //just one canView is enough to know that we can view the file
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//if the document isn't on any page, then allow viewing of the document (because there is no canView() to consult)
|
||||
$canView = true;
|
||||
}
|
||||
} else {
|
||||
//if the document isn't on any page, then allow viewing of the document (because there is no canView() to consult)
|
||||
$canView = true;
|
||||
}
|
||||
|
||||
// check for embargo or expiry
|
||||
if ($doc->isHidden()) $canView = false;
|
||||
|
||||
//admins can always download any document, even if otherwise hidden
|
||||
$member = Member::currentUser();
|
||||
if ($member && Permission::checkMember($member, 'ADMIN')) $canView = true;
|
||||
|
||||
if ($canView) {
|
||||
$path = $doc->getFullPath();
|
||||
if ( is_file($path) ) {
|
||||
|
211
code/DMSDocument_versions.php
Normal file
211
code/DMSDocument_versions.php
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/**
|
||||
* DataObject to store versions of uploaded Documents. Versions are only created when replacing a document, not on every
|
||||
* save of the DMSDocument dataobject. So, versions store the various versions of the underlying Document, not the
|
||||
* DataObject with information about that object.
|
||||
*/
|
||||
class DMSDocument_versions extends DataObject {
|
||||
|
||||
static $enable_versions = true; //flag that turns on or off versions of documents when replacing them
|
||||
|
||||
static $db = array(
|
||||
'VersionCounter' => 'Int',
|
||||
'VersionViewCount' => 'Int'
|
||||
); //config system call in _config creates this to mirror DMSDocument
|
||||
|
||||
static $has_one = array(
|
||||
'Document' => 'DMSDocument' //ID of the original DMSDocument object this is a version of
|
||||
);
|
||||
|
||||
static $defaults = array(
|
||||
'VersionCounter' => 0
|
||||
);
|
||||
|
||||
static $display_fields = array(
|
||||
'VersionCounter' => 'VersionCounter',
|
||||
'FilenameWithoutID' => 'Filename',
|
||||
'LastChanged' => 'LastChanged'
|
||||
);
|
||||
|
||||
static $summary_fields = array(
|
||||
'VersionCounter',
|
||||
'FilenameWithoutID'
|
||||
);
|
||||
|
||||
static $field_labels = array(
|
||||
'FilenameWithoutID'=>'Filename'
|
||||
);
|
||||
|
||||
static $default_sort = array(
|
||||
'LastChanged' => 'DESC'
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new version of a document by moving the current file and renaming it to the versioned filename.
|
||||
* This method assumes that the method calling this is just about to upload a new file to replace the old file.
|
||||
* @static
|
||||
* @param DMSDocument $doc
|
||||
* @return bool Success or failure
|
||||
*/
|
||||
static function create_version(DMSDocument $doc) {
|
||||
$success = false;
|
||||
|
||||
$existingPath = $doc->getFullPath();
|
||||
if (is_file($existingPath)) {
|
||||
$version = new DMSDocument_versions($doc); //create a copy of the current DMSDocument as a version
|
||||
|
||||
$previousVersionCounter = 0;
|
||||
$newestExistingVersion = self::get_versions($doc)->sort(array('Created'=>'DESC','ID'=>'DESC'))->limit(1);
|
||||
if ($newestExistingVersion && $newestExistingVersion->Count() > 0) {
|
||||
$previousVersionCounter = $newestExistingVersion->first()->VersionCounter;
|
||||
}
|
||||
|
||||
//change the filename field to a field containing the new soon-to-be versioned file
|
||||
$version->VersionCounter = $previousVersionCounter + 1; //start versions at 1
|
||||
$newFilename = $version->generateVersionedFilename($doc, $version->VersionCounter);
|
||||
$version->Filename = $newFilename;
|
||||
|
||||
//add a relation back to the origin ID;
|
||||
$version->DocumentID = $doc->ID;
|
||||
$id = $version->write();
|
||||
|
||||
if (!empty($id)) {
|
||||
rename($existingPath, $version->getFullPath());
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$path = $this->getFullPath();
|
||||
if (file_exists($path)) unlink($path);
|
||||
|
||||
parent::delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DataList of all previous Versions of a document (check the LastEdited date of each
|
||||
* object to find the correct one)
|
||||
* @static
|
||||
* @param DMSDocument $doc
|
||||
* @return DataList List of Document objects
|
||||
*/
|
||||
static function get_versions(DMSDocument $doc) {
|
||||
if (!DMSDocument_versions::$enable_versions) user_error("DMSDocument versions are disabled",E_USER_WARNING);
|
||||
return DMSDocument_versions::get()->filter(array('DocumentID' => $doc->ID));
|
||||
}
|
||||
|
||||
public function __construct($record = null, $isSingleton = false, $model = null) {
|
||||
//check what the constructor was passed
|
||||
$dmsObject = null;
|
||||
if ($record && is_subclass_of($record,'DMSDocumentInterface')) {
|
||||
$dmsObject = $record;
|
||||
$record = null; //cancel the record creation to just create an empty object
|
||||
}
|
||||
|
||||
//create the object
|
||||
parent::__construct($record, $isSingleton, $model);
|
||||
|
||||
//copy the DMSDocument object, if passed into the constructor
|
||||
if ($dmsObject) {
|
||||
foreach(array_keys(DataObject::custom_database_fields($dmsObject->ClassName)) as $key) {
|
||||
$this->$key = $dmsObject->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a link to download this document from the DMS store
|
||||
* @return String
|
||||
*/
|
||||
function getLink() {
|
||||
return Controller::join_links(Director::baseURL(),'dmsdocument/version'.$this->ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Document versions are always hidden from outside viewing. Only admins can download them
|
||||
* @return bool
|
||||
*/
|
||||
function isHidden() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full filename of the document stored in this object. Can optionally specify which filename to use at the end
|
||||
* @return string
|
||||
*/
|
||||
function getFullPath($filename = null) {
|
||||
if (!$filename) $filename = $this->Filename;
|
||||
return DMS::get_dms_path() . DIRECTORY_SEPARATOR . $this->Folder . DIRECTORY_SEPARATOR . $filename;
|
||||
}
|
||||
|
||||
function getFilenameWithoutID() {
|
||||
$filenameParts = explode('~',$this->Filename);
|
||||
$filename = array_pop($filenameParts);
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new filename for the current Document's file when replacing the current file with a new file
|
||||
* @param $filename The original filename to generate the versioned filename from
|
||||
* @return String The new filename
|
||||
*/
|
||||
protected function generateVersionedFilename(DMSDocument $doc, $versionCounter) {
|
||||
$filename = $doc->Filename;
|
||||
|
||||
do {
|
||||
$versionPaddingString = str_pad($versionCounter, 4, '0', STR_PAD_LEFT); //add leading zeros to make sorting accurate up to 10,000 documents
|
||||
$newVersionFilename = preg_replace('/([0-9]+~)(.*?)/','$1~'.$versionPaddingString.'~$2',$filename);
|
||||
|
||||
if ($newVersionFilename == $filename || empty($newVersionFilename)) { //sanity check for crazy document names
|
||||
user_error('Cannot generate new document filename for file: '.$filename,E_USER_ERROR);
|
||||
}
|
||||
|
||||
$versionCounter++; //increase the counter for the next loop run, if necessary
|
||||
} while(file_exists($this->getFullPath($newVersionFilename)));
|
||||
|
||||
return $newVersionFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extension of the file associated with the document
|
||||
*/
|
||||
function getExtension() {
|
||||
return strtolower(pathinfo($this->Filename, PATHINFO_EXTENSION));
|
||||
}
|
||||
|
||||
function getSize() {
|
||||
$size = $this->getAbsoluteSize();
|
||||
return ($size) ? File::format_size($size) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the file associated with the document
|
||||
*/
|
||||
function getAbsoluteSize() {
|
||||
return filesize($this->getFullPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias to DMSDocument::getSize()
|
||||
*/
|
||||
function getFileSizeFormatted(){
|
||||
return $this->getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
function trackView(){
|
||||
if ($this->ID > 0) {
|
||||
$count = $this->VersionViewCount + 1;
|
||||
DB::query("UPDATE \"DMSDocument_versions\" SET \"VersionViewCount\"='$count' WHERE \"ID\"={$this->ID}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
91
tests/DMSVersioningTest.php
Executable file
91
tests/DMSVersioningTest.php
Executable file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
class DMSVersioningTest extends SapphireTest {
|
||||
|
||||
static $testFile = 'dms/tests/DMS-test-lorum-file.pdf';
|
||||
static $testFile2 = 'dms/tests/DMS-test-document-2.pdf';
|
||||
|
||||
//store values to reset back to after this test runs
|
||||
static $dmsFolderOld;
|
||||
static $dmsFolderSizeOld;
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
self::$dmsFolderOld = DMS::$dmsFolder;
|
||||
self::$dmsFolderSizeOld = DMS::$dmsFolderSize;
|
||||
|
||||
//use a test DMS folder, so we don't overwrite the live one
|
||||
DMS::$dmsFolder = 'dms-assets-test-versions';
|
||||
|
||||
//clear out the test folder (in case a broken test doesn't delete it)
|
||||
$this->delete(BASE_PATH . DIRECTORY_SEPARATOR . 'dms-assets-test-versions');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
parent::tearDown();
|
||||
|
||||
$d = DataObject::get("DMSDocument");
|
||||
foreach($d as $d1) {
|
||||
$d1->delete();
|
||||
}
|
||||
$t = DataObject::get("DMSTag");
|
||||
foreach($t as $t1) {
|
||||
$t1->delete();
|
||||
}
|
||||
|
||||
//delete the test folder after the test runs
|
||||
$this->delete(BASE_PATH . DIRECTORY_SEPARATOR . 'dms-assets-test-versions');
|
||||
|
||||
//set the old DMS folder back again
|
||||
DMS::$dmsFolder = self::$dmsFolderOld;
|
||||
DMS::$dmsFolderSize = self::$dmsFolderSizeOld;
|
||||
}
|
||||
|
||||
public function delete($path) {
|
||||
if (file_exists($path) || is_dir($path)) {
|
||||
$it = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($path),
|
||||
RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
foreach ($it as $file) {
|
||||
if (in_array($file->getBasename(), array('.', '..'))) {
|
||||
continue;
|
||||
} elseif ($file->isDir()) {
|
||||
rmdir($file->getPathname());
|
||||
} elseif ($file->isFile() || $file->isLink()) {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
}
|
||||
rmdir($path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testDMSVersionStorage() {
|
||||
$dms = DMS::inst();
|
||||
|
||||
$document = $dms->storeDocument(self::$testFile);
|
||||
|
||||
$this->assertNotNull($document, "Document object created");
|
||||
$this->assertTrue(file_exists(DMS::get_dms_path() . DIRECTORY_SEPARATOR . $document->Folder . DIRECTORY_SEPARATOR . $document->Filename),"Document file copied into DMS folder");
|
||||
|
||||
$document->replaceDocument(self::$testFile2);
|
||||
$document->replaceDocument(self::$testFile);
|
||||
$document->replaceDocument(self::$testFile2);
|
||||
$document->replaceDocument(self::$testFile);
|
||||
|
||||
$versionsList = $document->getVersions();
|
||||
|
||||
$this->assertEquals(4, $versionsList->Count(),"4 Versions created");
|
||||
$versionsArray = $versionsList->toArray();
|
||||
|
||||
$this->assertEquals($versionsArray[0]->VersionCounter, 1,"Correct version count");
|
||||
$this->assertEquals($versionsArray[1]->VersionCounter, 2,"Correct version count");
|
||||
$this->assertEquals($versionsArray[2]->VersionCounter, 3,"Correct version count");
|
||||
$this->assertEquals($versionsArray[3]->VersionCounter, 4,"Correct version count");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user