diff --git a/filesystem/File.php b/filesystem/File.php index c687c768c..f0c65ff34 100755 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -180,7 +180,9 @@ class File extends DataObject { protected function onBeforeDelete() { parent::onBeforeDelete(); - $this->autosetFilename(); + // ensure that the record is synced with the filesystem before deleting + $this->updateFilesystem(); + if($this->Filename && $this->Name && file_exists($this->getFullPath()) && !is_dir($this->getFullPath())) { unlink($this->getFullPath()); } @@ -334,13 +336,76 @@ class File extends DataObject { /** * Event handler called before deleting from the database. * You can overload this to clean up or otherwise process data before delete this - * record. Don't forget to call parent::onBeforeWrite(), though! + * record. */ protected function onBeforeWrite() { parent::onBeforeWrite(); // Set default name - if(!$this->Name) $this->Name = "new-" . strtolower($this->class); + if(!$this->getField('Name')) $this->Name = "new-" . strtolower($this->class); + + // Set name on filesystem. If the current object is a "Folder", will also update references + // to subfolders and contained file records (both in database and filesystem) + $this->updateFilesystem(); + } + + /** + * Moving the file if appropriate according to updated database content. + * Throws an Exception if the new file already exists. + * + * Caution: This method should just be called during a {@link write()} invocation, + * as it relies on {@link DataObject->isChanged()}, which is reset after a {@link write()} call. + * Might be called as {@link File->updateFilesystem()} from within {@link Folder->updateFilesystem()}, + * so it has to handle both files and folders. + * + * Assumes that the "Filename" property was previously updated, either directly or indirectly. + * (it might have been influenced by {@link setName()} or {@link setParentID()} before). + */ + public function updateFilesystem() { + // Regenerate "Filename", just to be sure + $this->setField('Filename', $this->getRelativePath()); + + // If certain elements are changed, update the filesystem reference + if(!$this->isChanged('Filename')) return false; + + $changedFields = $this->getChangedFields(); + $pathBefore = $changedFields['Filename']['before']; + $pathAfter = $changedFields['Filename']['after']; + + // If the file or folder didn't exist before, don't rename - its created + if(!$pathBefore) return; + + $pathBeforeAbs = Director::getAbsFile($pathBefore); + $pathAfterAbs = Director::getAbsFile($pathAfter); + + // TODO Fix Filetest->testCreateWithFilenameWithSubfolder() to enable this + // // Create parent folders recursively in database and filesystem + // if(!is_a($this, 'Folder')) { + // $folder = Folder::findOrMake(dirname($pathAfterAbs)); + // if($folder) $this->ParentID = $folder->ID; + // } + + // Check that original file or folder exists, and rename on filesystem if required. + // The folder of the path might've already been renamed by Folder->updateFilesystem() + // before any filesystem update on contained file or subfolder records is triggered. + if(!file_exists($pathAfterAbs)) { + if(!is_a($this, 'Folder')) { + // Only throw a fatal error if *both* before and after paths don't exist. + if(!file_exists($pathBeforeAbs)) throw new Exception("Cannot move $pathBefore to $pathAfter - $pathBefore doesn't exist"); + + // Check that target directory (not the file itself) exists. + // Only check if we're dealing with a file, otherwise the folder will need to be created + if(!file_exists(dirname($pathAfterAbs))) throw new Exception("Cannot move $pathBefore to $pathAfter - Directory " . dirname($pathAfter) . " doesn't exist"); + } + + // Rename file or folder + $success = rename($pathBeforeAbs, $pathAfterAbs); + if(!$success) throw new Exception("Cannot move $pathBeforeAbs to $pathAfterAbs"); + } + + + // Update any database references + $this->updateLinks($pathBefore, $pathAfter); } /** @@ -362,11 +427,13 @@ class File extends DataObject { /** * Setter function for Name. Automatically sets a default title, - * and removes characters that migh be invalid on the filesystem. + * and removes characters that might be invalid on the filesystem. * Also adds a suffix to the name if the filename already exists * on the filesystem, and is associated to a different {@link File} database record * in the same folder. This means "myfile.jpg" might become "myfile-1.jpg". * + * Does not change the filesystem itself, please use {@link write()} for this. + * * @param String $name */ function setName($name) { @@ -404,61 +471,13 @@ class File extends DataObject { // Update actual field value $this->setField('Name', $name); - - if($oldName && $oldName != $this->Name) { - $this->resetFilename(); - } else { - $this->autosetFilename(); - } - + // Ensure that the filename is updated as well (only in-memory) + // Important: Circumvent the getter to avoid infinite loops + $this->setField('Filename', $this->getRelativePath()); return $this->getField('Name'); } - /** - * Change the "Filename" property based on the current "Name" property, moving the file if appropriate. - * Throws an Exception if the new file already exists. - * - * Caution: This method should just be called during a {@link write()} invocation, - * otherwise the database and filesystem might become out of sync. - * - * @param Boolean $renamePhysicalFile FALSE to avoiding renaming the file on the filesystem. - * Used when calling {@link resetFilename()} on the children of a folder. - */ - protected function resetFilename($renamePhysicalFile = true) { - $oldFilename = $this->getField('Filename'); // call without getter to get old value - $newFilename = $this->getRelativePath(); // calculated from $this->Name - - if($this->Name && $this->Filename && file_exists(Director::getAbsFile($oldFilename)) && strpos($oldFilename, '//') === false) { - if($renamePhysicalFile) { - $from = Director::getAbsFile($oldFilename); - $to = Director::getAbsFile($newFilename); - - // Error checking - if(!file_exists($from)) throw new Exception("Cannot move $oldFilename to $newFilename - $oldFilename doesn't exist"); - if(!file_exists(dirname($to))) throw new Exception("Cannot move $oldFilename to $newFilename - " . dirname($newFilename) . " doesn't exist"); - - // Rename file - $success = rename($from, $to); - if(!$success) throw new Exception("Cannot move $oldFilename to $newFilename"); - } - - $this->updateLinks($oldFilename, $newFilename); - } else { - // If the old file doesn't exist, maybe it's already been renamed. - if(file_exists(Director::getAbsFile($newFilename))) $this->updateLinks($oldFilename, $newFilename); - } - - $this->setField('Filename', $newFilename); - } - - /** - * Set the Filename field without manipulating the filesystem. - */ - protected function autosetFilename() { - $this->setField('Filename', $this->getRelativePath()); - } - /** * Rewrite links to the $old file to now point to the $new file. * @@ -480,11 +499,14 @@ class File extends DataObject { if(class_exists('Subsite')) Subsite::disable_subsite_filter(false); } + /** + * Does not change the filesystem itself, please use {@link write()} for this. + */ function setParentID($parentID) { $this->setField('ParentID', $parentID); - if($this->Name) $this->resetFilename(); - else $this->autosetFilename(); + // Don't change on the filesystem, we'll handle that in onBeforeWrite() + $this->setField('Filename', $this->getRelativePath()); return $this->getField('ParentID'); } @@ -538,9 +560,9 @@ class File extends DataObject { /** * Returns path relative to webroot. + * Serves as a "fallback" method to create the "Filename" property if it isn't set. * If no {@link Folder} is set ("ParentID" property), * defaults to a filename relative to the ASSETS_DIR (usually "assets/"). - * Use {@link getFullPath()} to * * @return String */ @@ -564,6 +586,7 @@ class File extends DataObject { } function getFilename() { + // Default behaviour: Return field if its set if($this->getField('Filename')) { return $this->getField('Filename'); } else { @@ -571,6 +594,9 @@ class File extends DataObject { } } + /** + * Does not change the filesystem itself, please use {@link write()} for this. + */ function setFilename($val) { $this->setField('Filename', $val); @@ -759,6 +785,10 @@ class File extends DataObject { return new ValidationResult(false, $message); } } + + // We aren't validating for an existing "Filename" on the filesystem. + // A record should still be saveable even if the underlying record has been removed. + return new ValidationResult(true); } diff --git a/filesystem/Filesystem.php b/filesystem/Filesystem.php index 429093e87..8d8c1f340 100755 --- a/filesystem/Filesystem.php +++ b/filesystem/Filesystem.php @@ -59,7 +59,7 @@ class Filesystem extends Object { $files = DataObject::get("File"); foreach($files as $file) { - $file->resetFilename(); + $file->updateFilesystem(); echo "
  • ", $file->Filename; $file->write(); } diff --git a/filesystem/Folder.php b/filesystem/Folder.php index 6766f0733..44ef4b735 100755 --- a/filesystem/Folder.php +++ b/filesystem/Folder.php @@ -7,6 +7,10 @@ * a folder object also updates all associated children * (both {@link File} and {@link Folder} records). * + * Deleting a folder will also remove the folder from the filesystem, + * including any subfolders and contained files. Use {@link deleteDatabaseOnly()} + * to avoid touching the filesystem. + * * See {@link File} documentation for more details about the * relationship between the database and filesystem in the sapphire file APIs. * @@ -260,8 +264,7 @@ class Folder extends File { function getRelativePath() { return parent::getRelativePath() . "/"; } - - + function onBeforeDelete() { if($this->ID && ($children = $this->AllChildren())) { foreach($children as $child) { @@ -280,6 +283,7 @@ class Folder extends File { if( !$files || ( count( $files ) == 1 && preg_match( '/\/_resampled$/', $files[0] ) ) ) Filesystem::removeFolder( $this->getFullPath() ); } + parent::onBeforeDelete(); } @@ -328,33 +332,16 @@ class Folder extends File { } /** - * Overload autosetFilename() to call autosetFilename() on all the children, too + * Overloaded to call recursively on all contained {@link File} records. */ - public function autosetFilename() { - parent::autosetFilename(); + public function updateFilesystem() { + parent::updateFilesystem(); + // Note: Folders will have been renamed on the filesystem already at this point, + // File->updateFilesystem() needs to take this into account. if($this->ID && ($children = $this->AllChildren())) { - $this->write(); - foreach($children as $child) { - $child->autosetFilename(); - $child->write(); - } - } - } - - /** - * Overload resetFilename() to call resetFilename() on all the children, too. - * Pass renamePhysicalFile = false, since the folder renaming will have taken care of this - */ - protected function resetFilename($renamePhysicalFile = true) { - parent::resetFilename($renamePhysicalFile); - - if($this->ID && ($children = $this->AllChildren())) { - $this->write(); - - foreach($children as $child) { - $child->resetFilename(false); + $child->updateFilesystem(); $child->write(); } } diff --git a/tests/FileLinkTrackingTest.php b/tests/FileLinkTrackingTest.php index 8fb5c3fb1..0dd701c83 100644 --- a/tests/FileLinkTrackingTest.php +++ b/tests/FileLinkTrackingTest.php @@ -34,6 +34,7 @@ class FileLinkTrackingTest extends SapphireTest { $file = $this->objFromFixture('File', 'file1'); $file->Name = 'renamed-test-file.pdf'; + $file->write(); $this->assertContains('ID")->value()); @@ -55,6 +56,7 @@ class FileLinkTrackingTest extends SapphireTest { // Rename the file $file = $this->objFromFixture('File', 'file1'); $file->Name = 'renamed-test-file.pdf'; + $file->write(); // Verify that the draft and publish virtual pages both have the corrected link $this->assertContains('objFromFixture('File', 'file1'); $file->Name = 'renamed-test-file.pdf'; + $file->write(); // Caching hack Versioned::prepopulate_versionnumber_cache('SiteTree', 'Stage', array($page->ID)); @@ -92,6 +95,9 @@ class FileLinkTrackingTest extends SapphireTest { $file->Name = 'renamed-test-file.pdf'; $file->write(); + // TODO Workaround for bug in DataObject->getChangedFields(), which returns stale data, + // and influences File->updateFilesystem() + $file = DataObject::get_by_id('File', $file->ID); $file->Name = 'renamed-test-file-second-time.pdf'; $file->write(); diff --git a/tests/filesystem/FileTest.php b/tests/filesystem/FileTest.php index c62069fbd..0f62ab7ed 100644 --- a/tests/filesystem/FileTest.php +++ b/tests/filesystem/FileTest.php @@ -7,6 +7,34 @@ class FileTest extends SapphireTest { static $fixture_file = 'sapphire/tests/filesystem/FileTest.yml'; + function testCreateWithFilenameWithSubfolder() { + // Note: We can't use fixtures/setUp() for this, as we want to create the db record manually. + // Creating the folder is necessary to avoid having "Filename" overwritten by setName()/setRelativePath(), + // because the parent folders don't exist in the database + $folder = Folder::findOrMake('/FileTest/'); + $testfilePath = 'assets/FileTest/CreateWithFilenameHasCorrectPath.txt'; // Important: No leading slash + $fh = fopen(BASE_PATH . '/' . $testfilePath, "w"); + fwrite($fh, str_repeat('x',1000000)); + fclose($fh); + + $file = new File(); + $file->Filename = $testfilePath; + // TODO This should be auto-detected + $file->ParentID = $folder->ID; + $file->write(); + + $this->assertEquals('CreateWithFilenameHasCorrectPath.txt', $file->Name, '"Name" property is automatically set from "Filename"'); + $this->assertEquals($testfilePath, $file->Filename, '"Filename" property remains unchanged'); + + // TODO This should be auto-detected, see File->updateFilesystem() + // $this->assertType('Folder', $file->Parent(), 'Parent folder is created in database'); + // $this->assertFileExists($file->Parent()->getFullPath(), 'Parent folder is created on filesystem'); + // $this->assertEquals('FileTest', $file->Parent()->Name); + // $this->assertType('Folder', $file->Parent()->Parent(), 'Grandparent folder is created in database'); + // $this->assertFileExists($file->Parent()->Parent()->getFullPath(), 'Grandparent folder is created on filesystem'); + // $this->assertEquals('assets', $file->Parent()->Parent()->Name); + } + function testGetExtension() { $this->assertEquals('', File::get_file_extension('myfile'), 'No extension'); $this->assertEquals('txt', File::get_file_extension('myfile.txt'), 'Simple extension'); @@ -35,12 +63,87 @@ class FileTest extends SapphireTest { File::$allowed_extensions = $origExts; } + function testSetNameChangesFilesystemOnWrite() { + $file = $this->objFromFixture('File', 'asdf'); + $oldPath = $file->getFullPath(); + + // Before write() + $file->Name = 'renamed.txt'; + $this->assertFileExists($oldPath, 'Old path is still present'); + $this->assertFileNotExists($file->getFullPath(), 'New path is updated in memory, not written before write() is called'); + + $file->write(); + + // After write() + clearstatcache(); + $this->assertFileNotExists($oldPath, 'Old path is removed after write()'); + $this->assertFileExists($file->getFullPath(), 'New path is created after write()'); + } + + function testSetParentIDChangesFilesystemOnWrite() { + $file = $this->objFromFixture('File', 'asdf'); + $subfolder = $this->objFromFixture('Folder', 'subfolder'); + $oldPath = $file->getFullPath(); + + // set ParentID + $file->ParentID = $subfolder->ID; + + // Before write() + $this->assertFileExists($oldPath, 'Old path is still present'); + $this->assertFileNotExists($file->getFullPath(), 'New path is updated in memory, not written before write() is called'); + + $file->write(); + + // After write() + clearstatcache(); + $this->assertFileNotExists($oldPath, 'Old path is removed after write()'); + $this->assertFileExists($file->getFullPath(), 'New path is created after write()'); + } + + /** + * @see http://open.silverstripe.org/ticket/5693 + */ + function testSetNameWithInvalidExtensionDoesntChangeFilesystem() { + $origExts = File::$allowed_extensions; + File::$allowed_extensions = array('txt'); + + $file = $this->objFromFixture('File', 'asdf'); + $oldPath = $file->getFullPath(); + + $file->Name = 'renamed.php'; // evil extension + try { + $file->write(); + } catch(ValidationException $e) { + File::$allowed_extensions = $origExts; + return; + } + + $this->fail('Expected ValidationException not raised'); + File::$allowed_extensions = $origExts; + } + function testLinkAndRelativeLink() { $file = $this->objFromFixture('File', 'asdf'); $this->assertEquals(ASSETS_DIR . '/FileTest.txt', $file->RelativeLink()); $this->assertEquals(Director::baseURL() . ASSETS_DIR . '/FileTest.txt', $file->Link()); } + function testGetRelativePath() { + $rootfile = $this->objFromFixture('File', 'asdf'); + $this->assertEquals('assets/FileTest.txt', $rootfile->getRelativePath(), 'File in assets/ folder'); + + $subfolderfile = $this->objFromFixture('File', 'subfolderfile'); + $this->assertEquals('assets/FileTest-subfolder/FileTestSubfolder.txt', $subfolderfile->getRelativePath(), 'File in subfolder within assets/ folder, with existing Filename'); + + $subfolderfilesetfromname = $this->objFromFixture('File', 'subfolderfile-setfromname'); + $this->assertEquals('assets/FileTest-subfolder/FileTestSubfolder2.txt', $subfolderfilesetfromname->getRelativePath(), 'File in subfolder within assets/ folder, with Filename generated through setName()'); + } + + function testGetFullPath() { + $rootfile = $this->objFromFixture('File', 'asdf'); + $this->assertEquals(ASSETS_PATH . '/FileTest.txt', $rootfile->getFullPath(), 'File in assets/ folder'); + } + function testNameAndTitleGeneration() { /* If objects are loaded into the system with just a Filename, then Name is generated but Title isn't */ $file = $this->objFromFixture('File', 'asdf'); @@ -52,38 +155,7 @@ class FileTest extends SapphireTest { $this->assertEquals(ASSETS_DIR . '/FileTest.png', $file->Filename); $this->assertEquals('FileTest', $file->Title); } - - function testChangingNameAndFilenameAndParentID() { - $file = $this->objFromFixture('File', 'asdf'); - - /* If you alter the Name attribute of a file, then the filesystem is also affected */ - $file->Name = 'FileTest2.txt'; - clearstatcache(); - $this->assertFileNotExists(ASSETS_PATH . "/FileTest.txt"); - $this->assertFileExists(ASSETS_PATH . "/FileTest2.txt"); - /* The Filename field is also updated */ - $this->assertEquals(ASSETS_DIR . '/FileTest2.txt', $file->Filename); - /* However, if you alter the Filename attribute, the the filesystem isn't affected. Altering Filename directly isn't - recommended */ - $file->Filename = ASSETS_DIR . '/FileTest3.txt'; - clearstatcache(); - $this->assertFileExists(ASSETS_PATH . "/FileTest2.txt"); - $this->assertFileNotExists(ASSETS_PATH . "/FileTest3.txt"); - - $file->Filename = ASSETS_DIR . '/FileTest2.txt'; - $file->write(); - - /* Instead, altering Name and ParentID is the recommended way of changing the name and location of a file */ - $file->ParentID = $this->idFromFixture('Folder', 'subfolder'); - clearstatcache(); - $this->assertFileExists(ASSETS_PATH . "/subfolder/FileTest2.txt"); - $this->assertFileNotExists(ASSETS_PATH . "/FileTest2.txt"); - $this->assertEquals(ASSETS_DIR . '/subfolder/FileTest2.txt', $file->Filename); - $file->write(); - - } - function testSizeAndAbsoluteSizeParameters() { $file = $this->objFromFixture('File', 'asdf'); @@ -96,10 +168,10 @@ class FileTest extends SapphireTest { function testFileType() { $file = $this->objFromFixture('File', 'gif'); $this->assertEquals("GIF image - good for diagrams", $file->FileType); - + $file = $this->objFromFixture('File', 'pdf'); $this->assertEquals("Adobe Acrobat PDF file", $file->FileType); - + /* Only a few file types are given special descriptions; the rest are unknown */ $file = $this->objFromFixture('File', 'asdf'); $this->assertEquals("unknown", $file->FileType); @@ -123,6 +195,19 @@ class FileTest extends SapphireTest { $this->assertEquals("93132.3 GB", File::format_size(100000000000000)); } + function testDeleteDatabaseOnly() { + $file = $this->objFromFixture('File', 'asdf'); + $fileID = $file->ID; + $filePath = $file->getFullPath(); + + $file->deleteDatabaseOnly(); + + DataObject::flush_and_destroy_cache(); + + $this->assertFileExists($filePath); + $this->assertFalse(DataObject::get_by_id('File', $fileID)); + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// function setUp() { @@ -159,7 +244,7 @@ class FileTest extends SapphireTest { $folderIDs = $this->allFixtureIDs('Folder'); foreach($folderIDs as $folderID) { $folder = DataObject::get_by_id('Folder', $folderID); - if($folder && file_exists(BASE_PATH."/$folder->Filename")) rmdir(BASE_PATH."/$folder->Filename"); + if($folder && file_exists(BASE_PATH."/$folder->Filename")) Filesystem::removeFolder(BASE_PATH."/$folder->Filename"); } parent::tearDown(); diff --git a/tests/filesystem/FileTest.yml b/tests/filesystem/FileTest.yml index c78a22dcd..5835c3608 100644 --- a/tests/filesystem/FileTest.yml +++ b/tests/filesystem/FileTest.yml @@ -1,3 +1,13 @@ +Folder: + subfolder: + Name: FileTest-subfolder + folder1: + Name: FileTest-folder1 + folder2: + Name: FileTest-folder2 + folder1-subfolder1: + Name: FileTest-folder1-subfolder1 + ParentID: =>Folder.folder1 File: asdf: Filename: assets/FileTest.txt @@ -8,7 +18,13 @@ File: setfromname: Name: FileTest.png ParentID: 0 - -Folder: - subfolder: - Name: subfolder \ No newline at end of file + subfolderfile: + Filename: assets/FileTest-subfolder/FileTestSubfolder.txt + ParentID: =>Folder.subfolder + subfolderfile-setfromname: + Name: FileTestSubfolder2.txt + ParentID: =>Folder.subfolder + file1-folder1: + Filename: assets/FileTest-folder1/File1.txt + Name: File1.txt + ParentID: =>Folder.folder1 \ No newline at end of file diff --git a/tests/filesystem/FolderTest.php b/tests/filesystem/FolderTest.php index 775278946..b08012ca9 100644 --- a/tests/filesystem/FolderTest.php +++ b/tests/filesystem/FolderTest.php @@ -8,13 +8,29 @@ */ class FolderTest extends SapphireTest { - function tearDown() { - $testPath = ASSETS_PATH . '/FolderTest'; - if(file_exists($testPath)) Filesystem::removeFolder($testPath); + static $fixture_file = 'sapphire/tests/filesystem/FileTest.yml'; + + function testCreateFromNameAndParentIDSetsFilename() { + $folder1 = $this->objFromFixture('Folder', 'folder1'); + $newFolder = new Folder(); + $newFolder->Name = 'CreateFromNameAndParentID'; + $newFolder->ParentID = $folder1->ID; + $newFolder->write(); - parent::tearDown(); + $this->assertEquals($folder1->Filename . 'CreateFromNameAndParentID/', $newFolder->Filename); } + function testAllChildrenIncludesFolders() { + $folder1 = $this->objFromFixture('Folder', 'folder1'); + $subfolder1 = $this->objFromFixture('Folder', 'folder1-subfolder1'); + $file1 = $this->objFromFixture('File', 'file1-folder1'); + + $children = $folder1->allChildren(); + $this->assertEquals(2, $children->Count()); + $this->assertContains($subfolder1->ID, $children->column('ID')); + $this->assertContains($file1->ID, $children->column('ID')); + } + function testFindOrMake() { $path = '/FolderTest/testFindOrMake/'; $folder = Folder::findOrMake($path); @@ -33,4 +49,201 @@ class FolderTest extends SapphireTest { 'Path information is correctly saved to database (without trailing slash)' ); } + + /** + * @see FileTest->testSetNameChangesFilesystemOnWrite() + */ + function testSetNameChangesFilesystemOnWrite() { + $folder1 = $this->objFromFixture('Folder', 'folder1'); + $subfolder1 = $this->objFromFixture('Folder', 'folder1-subfolder1'); + $file1 = $this->objFromFixture('File', 'file1-folder1'); + $oldPathFolder1 = $folder1->getFullPath(); + $oldPathSubfolder1 = $subfolder1->getFullPath(); + $oldPathFile1 = $file1->getFullPath(); + + // Before write() + $folder1->Name = 'FileTest-folder1-renamed'; + $this->assertFileExists($oldPathFolder1, 'Old path is still present'); + $this->assertFileNotExists($folder1->getFullPath(), 'New path is updated in memory, not written before write() is called'); + $this->assertFileExists($oldPathFile1, 'Old file is still present'); + // TODO setters currently can't update in-memory + // $this->assertFileNotExists($file1->getFullPath(), 'New path on contained files is updated in memory, not written before write() is called'); + // $this->assertFileNotExists($subfolder1->getFullPath(), 'New path on subfolders is updated in memory, not written before write() is called'); + + $folder1->write(); + + // After write() + + // Reload state + clearstatcache(); + DataObject::flush_and_destroy_cache(); + $folder1 = DataObject::get_by_id('Folder', $folder1->ID); + $file1 = DataObject::get_by_id('File', $file1->ID); + $subfolder1 = DataObject::get_by_id('Folder', $subfolder1->ID); + + $this->assertFileNotExists($oldPathFolder1, 'Old path is removed after write()'); + $this->assertFileExists($folder1->getFullPath(), 'New path is created after write()'); + $this->assertFileNotExists($oldPathFile1, 'Old file is removed after write()'); + $this->assertFileExists($file1->getFullPath(), 'New file path is created after write()'); + $this->assertFileNotExists($oldPathSubfolder1, 'Subfolder is removed after write()'); + $this->assertFileExists($subfolder1->getFullPath(), 'New subfolder path is created after write()'); + + // Clean up after ourselves - tearDown() doesn't like renamed fixtures + $folder1->delete(); // implicitly deletes subfolder as well + } + + /** + * @see FileTest->testSetParentIDChangesFilesystemOnWrite() + */ + function testSetParentIDChangesFilesystemOnWrite() { + $folder1 = $this->objFromFixture('Folder', 'folder1'); + $folder2 = $this->objFromFixture('Folder', 'folder2'); + $oldPathFolder1 = $folder1->getFullPath(); + + // set ParentID + $folder1->ParentID = $folder2->ID; + + // Before write() + $this->assertFileExists($oldPathFolder1, 'Old path is still present'); + $this->assertFileNotExists($folder1->getFullPath(), 'New path is updated in memory, not written before write() is called'); + + $folder1->write(); + + // After write() + clearstatcache(); + $this->assertFileNotExists($oldPathFolder1, 'Old path is removed after write()'); + $this->assertFileExists($folder1->getFullPath(), 'New path is created after write()'); + } + + /** + * @see FileTest->testLinkAndRelativeLink() + */ + function testLinkAndRelativeLink() { + $folder = $this->objFromFixture('Folder', 'folder1'); + $this->assertEquals(ASSETS_DIR . '/FileTest-folder1/', $folder->RelativeLink()); + $this->assertEquals(Director::baseURL() . ASSETS_DIR . '/FileTest-folder1/', $folder->Link()); + } + + /** + * @see FileTest->testGetRelativePath() + */ + function testGetRelativePath() { + $rootfolder = $this->objFromFixture('Folder', 'folder1'); + $this->assertEquals('assets/FileTest-folder1/', $rootfolder->getRelativePath(), 'Folder in assets/'); + } + + /** + * @see FileTest->testGetFullPath() + */ + function testGetFullPath() { + $rootfolder = $this->objFromFixture('Folder', 'folder1'); + $this->assertEquals(ASSETS_PATH . '/FileTest-folder1/', $rootfolder->getFullPath(), 'File in assets/ folder'); + } + + function testDeleteAlsoRemovesFilesystem() { + $path = '/FolderTest/DeleteAlsoRemovesFilesystemAndChildren'; + $folder = Folder::findOrMake($path); + $this->assertFileExists(ASSETS_PATH . $path); + + $folder->delete(); + + $this->assertFileNotExists(ASSETS_PATH . $path); + } + + function testDeleteAlsoRemovesSubfoldersInDatabaseAndFilesystem() { + $path = '/FolderTest/DeleteAlsoRemovesSubfoldersInDatabaseAndFilesystem'; + $subfolderPath = $path . '/subfolder'; + $folder = Folder::findOrMake($path); + $subfolder = Folder::findOrMake($subfolderPath); + $subfolderID = $subfolder->ID; + + $folder->delete(); + + $this->assertFileNotExists(ASSETS_PATH . $path); + $this->assertFileNotExists(ASSETS_PATH . $subfolderPath, 'Subfolder removed from filesystem'); + $this->assertFalse(DataObject::get_by_id('Folder', $subfolderID), 'Subfolder removed from database'); + } + + function testDeleteAlsoRemovesContainedFilesInDatabaseAndFilesystem() { + $path = '/FolderTest/DeleteAlsoRemovesContainedFilesInDatabaseAndFilesystem'; + $folder = Folder::findOrMake($path); + + $file = $this->objFromFixture('File', 'gif'); + $file->ParentID = $folder->ID; + $file->write(); + $fileID = $file->ID; + $fileAbsPath = $file->getFullPath(); + $this->assertFileExists($fileAbsPath); + + $folder->delete(); + + $this->assertFileNotExists($fileAbsPath, 'Contained files removed from filesystem'); + $this->assertFalse(DataObject::get_by_id('File', $fileID), 'Contained files removed from database'); + + } + + /** + * @see FileTest->testDeleteDatabaseOnly() + */ + function testDeleteDatabaseOnly() { + $subfolder = $this->objFromFixture('Folder', 'subfolder'); + $subfolderID = $subfolder->ID; + $subfolderFile = $this->objFromFixture('File', 'subfolderfile'); + $subfolderFileID = $subfolderFile->ID; + + $subfolder->deleteDatabaseOnly(); + + DataObject::flush_and_destroy_cache(); + + $this->assertFileExists($subfolder->getFullPath()); + $this->assertFalse(DataObject::get_by_id('Folder', $subfolderID)); + + $this->assertFileExists($subfolderFile->getFullPath()); + $this->assertFalse(DataObject::get_by_id('File', $subfolderFileID)); + } + + function setUp() { + parent::setUp(); + + if(!file_exists(ASSETS_PATH)) mkdir(ASSETS_PATH); + + // Create a test folders for each of the fixture references + $folderIDs = $this->allFixtureIDs('Folder'); + foreach($folderIDs as $folderID) { + $folder = DataObject::get_by_id('Folder', $folderID); + if(!file_exists(BASE_PATH."/$folder->Filename")) mkdir(BASE_PATH."/$folder->Filename"); + } + + // Create a test files for each of the fixture references + $fileIDs = $this->allFixtureIDs('File'); + foreach($fileIDs as $fileID) { + $file = DataObject::get_by_id('File', $fileID); + $fh = fopen(BASE_PATH."/$file->Filename", "w"); + fwrite($fh, str_repeat('x',1000000)); + fclose($fh); + } + } + + function tearDown() { + $testPath = ASSETS_PATH . '/FolderTest'; + if(file_exists($testPath)) Filesystem::removeFolder($testPath); + + /* Remove the test files that we've created */ + $fileIDs = $this->allFixtureIDs('File'); + foreach($fileIDs as $fileID) { + $file = DataObject::get_by_id('File', $fileID); + if($file && file_exists(BASE_PATH."/$file->Filename")) unlink(BASE_PATH."/$file->Filename"); + } + + // Remove the test folders that we've crated + $folderIDs = $this->allFixtureIDs('Folder'); + foreach($folderIDs as $folderID) { + $folder = DataObject::get_by_id('Folder', $folderID); + // Might have been removed during test + if($folder && file_exists(BASE_PATH."/$folder->Filename")) Filesystem::removeFolder(BASE_PATH."/$folder->Filename"); + } + + parent::tearDown(); + } + } \ No newline at end of file