diff --git a/core/model/Image.php b/core/model/Image.php
index 004b652a7..5e97147b6 100755
--- a/core/model/Image.php
+++ b/core/model/Image.php
@@ -73,6 +73,8 @@ class Image extends File {
/**
* An image exists if it has a filename.
+ * Does not do any filesystem checks.
+ *
* @return boolean
*/
public function exists() {
@@ -82,7 +84,9 @@ class Image extends File {
}
/**
- * Return an XHTML img tag for this Image.
+ * Return an XHTML img tag for this Image,
+ * or NULL if the image file doesn't exist on the filesystem.
+ *
* @return string
*/
function getTag() {
@@ -100,6 +104,7 @@ class Image extends File {
/**
* Return an XHTML img tag for this Image.
+ *
* @return string
*/
function forTemplate() {
diff --git a/filesystem/File.php b/filesystem/File.php
index d851a9a57..507e13c6c 100755
--- a/filesystem/File.php
+++ b/filesystem/File.php
@@ -1,14 +1,63 @@
Security
+ *
* Caution: It is recommended to disable any script execution in the "assets/"
* directory in the webserver configuration, to reduce the risk of exploits.
* See http://doc.silverstripe.org/secure-development#filesystem
*
+ * Properties
+ *
+ * - "Name": File name (including extension) or folder name.
+ * Should be the same as the actual filesystem.
+ * - "Title": Optional title of the file (for display purposes only).
+ * Defaults to "Name".
+ * - "Filename": Path of the file or folder, relative to the webroot.
+ * Usually starts with the "assets/" directory, and has no trailing slash.
+ * Defaults to the "assets/" directory plus "Name" property if not set.
+ * Setting the "Filename" property will override the "Name" property.
+ * The value should be in sync with "ParentID".
+ * - "Content": Typically unused, but handy for a textual representation of
+ * files, e.g. for fulltext indexing of PDF documents.
+ * - "ParentID": Points to a {@link Folder} record. Should be in sync with
+ * "Filename". A ParentID=0 value points to the "assets/" folder, not the webroot.
+ *
+ * Synchronization
+ *
+ * Changes to a File database record can change the filesystem entry,
+ * but not the other way around. If the filesystem path is renamed outside
+ * of SilverStripe, there's no way for the database to recover this linkage.
+ * New physical files on the filesystem can be "discovered" via {@link Filesystem::sync()},
+ * the equivalent {@link File} and {@link Folder} records are automatically
+ * created by this method.
+ *
+ * Certain property changes within the File API that can cause a "delayed" filesystem change:
+ * The change is enforced in {@link onBeforeWrite()} later on.
+ * - setParentID()
+ * - setFilename()
+ * - setName()
+ * It is recommended that you use {@link write()} directly after setting any of these properties,
+ * otherwise getters like {@link getFullPath()} and {@link getRelativePath()}
+ * will result paths that are inconsistent with the filesystem.
+ *
+ * Caution: Calling {@link delete()} will also delete from the filesystem.
+ * Call {@link deleteDatabaseOnly()} if you want to avoid this.
+ *
+ * Creating Files and Folders
+ *
+ * Typically both files and folders should be created first on the filesystem,
+ * and then reflected in as database records. Folders can be created recursively
+ * from sapphire both in the database and filesystem through {@link Folder::findOrMake()}.
+ * Ensure that you always set a "Filename" property when writing to the database,
+ * leaving it out can lead to unexpected results.
+ *
* @package sapphire
* @subpackage filesystem
*/
@@ -79,16 +128,18 @@ class File extends DataObject {
/**
* Find a File object by the given filename.
+ *
+ * @param String $filename Matched against the "Name" property.
* @return mixed null if not found, File object of found file
*/
static function find($filename) {
// Get the base file if $filename points to a resampled file
$filename = ereg_replace('_resampled/[^-]+-','',$filename);
+ // Split to folders and the actual filename, and traverse the structure.
$parts = explode("/", $filename);
$parentID = 0;
$item = null;
-
foreach($parts as $part) {
if($part == ASSETS_DIR && !$parentID) continue;
$SQL_part = Convert::raw2sql($part);
@@ -112,7 +163,11 @@ class File extends DataObject {
return $this->Title;
}
- // Used by AssetTableField
+ /**
+ * @todo Unnecessary shortcut for AssetTableField, coupled with cms module.
+ *
+ * @return Integer
+ */
function BackLinkTrackingCount() {
return $this->BackLinkTracking()->Count();
}
@@ -120,7 +175,7 @@ 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::onBeforeDelete(), though!
+ * record. Don't forget to call {@link parent::onBeforeDelete()}, though!
*/
protected function onBeforeDelete() {
parent::onBeforeDelete();
@@ -131,6 +186,9 @@ class File extends DataObject {
}
}
+ /**
+ * Updates link tracking.
+ */
protected function onAfterDelete() {
parent::onAfterDelete();
@@ -204,6 +262,14 @@ class File extends DataObject {
return $this->canEdit($member);
}
+ /**
+ * Returns a category based on the file extension.
+ * This can be useful when grouping files by type,
+ * showing icons on filelinks, etc.
+ * Possible group values are: "audio", "mov", "zip", "image".
+ *
+ * @return String
+ */
public function appCategory() {
$ext = $this->Extension;
switch($ext) {
@@ -232,7 +298,11 @@ class File extends DataObject {
}
/**
- * Return the URL of an icon for the file type
+ * Return the relative URL of an icon for the file type,
+ * based on the {@link appCategory()} value.
+ * Images are searched for in "sapphire/images/app_icons/".
+ *
+ * @return String
*/
function Icon() {
$ext = $this->Extension;
@@ -269,6 +339,7 @@ class File extends DataObject {
protected function onBeforeWrite() {
parent::onBeforeWrite();
+ // Set default name
if(!$this->Name) $this->Name = "new-" . strtolower($this->class);
}
@@ -290,19 +361,25 @@ class File extends DataObject {
}
/**
- * Setter function for Name.
- * Automatically sets a default title.
+ * Setter function for Name. Automatically sets a default title,
+ * and removes characters that migh 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".
+ *
+ * @param String $name
*/
function setName($name) {
$oldName = $this->Name;
- // It can't be blank
+ // It can't be blank, default to Title
if(!$name) $name = $this->Title;
// Fix illegal characters
- $name = ereg_replace(' +','-',trim($name));
- $name = ereg_replace('[^A-Za-z0-9.+_\-]','',$name);
+ $name = ereg_replace(' +','-',trim($name)); // Replace any spaces
+ $name = ereg_replace('[^A-Za-z0-9.+_\-]','',$name); // Replace non alphanumeric characters
+ // Remove all leading dots or underscores
while(!empty($name) && ($name[0] == '_' || $name[0] == '.')) {
$name = substr($name, 1);
}
@@ -326,27 +403,36 @@ class File extends DataObject {
}
}
+ // Update title
if(!$this->getField('Title')) $this->__set('Title', str_replace(array('-','_'),' ',ereg_replace('\.[^.]+$','',$name)));
+
+ // Update actual field value
$this->setField('Name', $name);
-
-
+
+
if($oldName && $oldName != $this->Name) {
$this->resetFilename();
} else {
$this->autosetFilename();
}
-
-
+
+
return $this->getField('Name');
}
/**
- * Change a filename, moving the file if appropriate.
- * @param $renamePhysicalFile Set this to false if you don't want to rename the physical file. Used when calling resetFilename() on the children of a folder.
+ * 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');
- $newFilename = $this->getRelativePath();
+ $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) {
@@ -383,7 +469,12 @@ class File extends DataObject {
}
/**
- * Rewrite links to the $old file to now point to the $new file
+ * Rewrite links to the $old file to now point to the $new file.
+ *
+ * @uses SiteTree->rewriteFileURL()
+ *
+ * @param String $old File path relative to the webroot
+ * @param String $new File path relative to the webroot
*/
protected function updateLinks($old, $new) {
if(class_exists('Subsite')) Subsite::disable_subsite_filter(true);
@@ -434,6 +525,12 @@ class File extends DataObject {
return "$this->Name";
}
+ /**
+ * Returns an absolute filesystem path to the file.
+ * Use {@link getRelativePath()} to get the same path relative to the webroot.
+ *
+ * @return String
+ */
function getFullPath() {
$baseFolder = Director::baseFolder();
@@ -447,7 +544,10 @@ class File extends DataObject {
}
/**
- * Returns
+ * Returns path relative to webroot.
+ * 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
*/
@@ -463,6 +563,9 @@ class File extends DataObject {
}
}
+ /**
+ * @todo Coupling with cms module, remove this method.
+ */
function DeleteLink() {
return Director::absoluteBaseURL()."admin/assets/removefile/".$this->ID;
}
@@ -477,13 +580,20 @@ class File extends DataObject {
function setFilename($val) {
$this->setField('Filename', $val);
+
+ // "Filename" is the "master record" (existing on the filesystem),
+ // meaning we have to adjust the "Name" property in the database as well.
$this->setField('Name', basename($val));
}
- /*
- * FIXME This overrides getExtension() in DataObject, but it does something completely different.
+ /**
+ * Returns the file extension
+ *
+ * @todo This overrides getExtension() in DataObject, but it does something completely different.
* This should be renamed to getFileExtension(), but has not been yet as it may break
* legacy code.
+ *
+ * @return String
*/
function getExtension() {
return self::get_file_extension($this->getField('Filename'));
diff --git a/filesystem/Filesystem.php b/filesystem/Filesystem.php
index af4820751..429093e87 100755
--- a/filesystem/Filesystem.php
+++ b/filesystem/Filesystem.php
@@ -1,6 +1,7 @@
Done!";
}
- /*
+ /**
* Return the most recent modification time of anything in the folder.
+ *
* @param $folder The folder, relative to the site root
* @param $extensionList An option array of file extensions to limit the search to
+ * @return String Same as filemtime() format.
*/
static function folderModTime($folder, $extensionList = null, $recursiveCall = false) {
//$cacheID = $folder . ',' . implode(',', $extensionList);
@@ -93,7 +103,10 @@ class Filesystem extends Object {
/**
* Returns true if the given filename is an absolute file reference.
- * Works on Linux and Windows
+ * Works on Linux and Windows.
+ *
+ * @param String $filename Absolute or relative filename, with or without path.
+ * @return Boolean
*/
static function isAbsolute($filename) {
if($_ENV['OS'] == "Windows_NT" || $_SERVER['WINDIR']) return $filename[1] == ':' && $filename[2] == '/';
@@ -107,6 +120,9 @@ class Filesystem extends Object {
* If the given Folder ID isn't found, or not specified at all, then everything will
* be synchronised from the root folder (singleton Folder).
*
+ * See {@link File->updateFilesystem()} to sync properties of a single database record
+ * back to the equivalent filesystem record.
+ *
* @param int $folderID Folder ID to sync along with all it's children
*/
static function sync($folderID = null) {
diff --git a/filesystem/Folder.php b/filesystem/Folder.php
index 69e156b70..6766f0733 100755
--- a/filesystem/Folder.php
+++ b/filesystem/Folder.php
@@ -1,6 +1,14 @@