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