Version: current

      1 <?php
      2 /**
      3  * This class handles the representation of a File within Sapphire
      4  * Note: The files are stored in the assets/ directory, but sapphire
      5  * looks at the db object to gather information about a file such as URL
      6  * It then uses this for all processing functions (like image manipulation).
      7  *
      8  * @package sapphire
      9  * @subpackage filesystem
     10  */
     11 class File extends DataObject {
     12
     13     static $default_sort = "\"Name\"";
     14
     15     static $singular_name = "File";
     16
     17     static $plural_name = "Files";
     18
     19     static $db = array(
     20         "Name" => "Varchar(255)",
     21         "Title" => "Varchar(255)",
     22         "Filename" => "Varchar(255)",
     23         "Content" => "Text",
     24         "Sort" => "Int",
     25         "FileSize" => "Int",
     26         "Hidden" => "Boolean", // hide File/Folder from CMS, to avoid lags, when there are a lot
     27     );
     28
     29     static $has_one = array(
     30         "Parent" => "File",
     31         "Owner" => "Member"
     32     );
     33
     34     static $has_many = array();
     35
     36     static $many_many = array();
     37
     38     static $belongs_many_many = array();
     39
     40     static $defaults = array();
     41
     42     static $extensions = array(
     43         "Hierarchy",
     44     );
     45
     46     static $indexes = array(
     47         "Filename" => true,
     48     );
     49
     50     /**
     51      * @see Upload->allowedExtensions
     52      * @var array
     53      */
     54     public static $allowed_extensions = array(
     55         '','html','htm','xhtml','js','css',
     56         'bmp','png','gif','jpg','jpeg','ico','pcx','tif','tiff',
     57         'au','mid','midi','mpa','mp3','ogg','m4a','ra','wma','wav','cda',
     58         'avi','mpg','mpeg','asf','wmv','m4v','mov','mkv','mp4','swf','flv','ram','rm',
     59         'doc','docx','rtf','xls','xlsx','pages', 'odt', 'ods',
     60         'ppt','pptx','pps', 'odp', 'odg', 'fodp', 'uop', 'fodg',
     61         'cab','arj','tar','zip','zipx','sit','sitx','gz','tgz','bz2','ace','arc','pkg','dmg','hqx','jar', 'rar', '7z',
     62         'xml','pdf', 'csv', 'txt',
     63         'eps', 'svg', 'cdr', 'ai',
     64     );
     65
     66     /**
     67      * If this is true, then restrictions set in $allowed_max_file_size and
     68      * $allowed_extensions will be applied to users with admin privileges as
     69      * well.
     70      */
     71     public static $apply_restrictions_to_admin = true;
     72
     73
     74     /**
     75      * Cached result of a "SHOW FIELDS" call
     76      * in instance_get() for performance reasons.
     77      *
     78      * @var array
     79      */
     80     protected static $cache_file_fields = null;
     81
     82     /**
     83      * Find a File object by the given filename.
     84      * @return mixed null if not found, File object of found file
     85      */
     86     static function find($filename) {
     87         // Get the base file if $filename points to a resampled file
     88         $filename = ereg_replace('_resampled/[^-]+-','',$filename);
     89
     90         $parts = explode("/", $filename);
     91         $parentID = 0;
     92         $item = null;
     93
     94         foreach($parts as $part) {
     95             if($part == "assets" && !$parentID) continue;
     96             $SQL_part = Convert::raw2sql($part);
     97             $item = DataObject::get_one("File", "\"Name\" = '$SQL_part' AND \"ParentID\" = $parentID");
     98             if(!$item) break;
     99             $parentID = $item->ID;
    100         }
    101
    102         return $item;
    103     }
    104
    105     function Link($action = null) {
    106         return Director::absoluteBaseURL() . $this->RelativeLink($action);
    107     }
    108
    109     function RelativeLink($action = null){
    110         return $this->Filename;
    111     }
    112
    113     function TreeTitle() {
    114         return $this->Title;
    115     }
    116
    117     // Used by AssetTableField
    118     function BackLinkTrackingCount() {
    119         return $this->getUsageCount();
    120     }
    121
    122     /**
    123      * Event handler called before deleting from the database.
    124      * You can overload this to clean up or otherwise process data before delete this
    125      * record.  Don't forget to call parent::onBeforeDelete(), though!
    126      */
    127     protected function onBeforeDelete() {
    128         parent::onBeforeDelete();
    129
    130         $this->autosetFilename();
    131         if($this->Filename && $this->Name && file_exists($this->getFullPath()) && !is_dir($this->getFullPath())) {
    132             unlink($this->getFullPath());
    133         }
    134     }
    135
    136     protected function onAfterDelete() {
    137         parent::onAfterDelete();
    138
    139          /* do it in FileDataObjectTrackingDecorator.php
    140         if($tracking = $this->BackFileTracking()) {
    141             foreach($tracking as $entry) {
    142                 if (($page = $entry->LinkedObject()) && $page->is_a('SiteTree')) {
    143                     $origStage = Versioned::current_stage();
    144
    145                     // This will syncLinkTracking on draft
    146                     Versioned::reading_stage('Stage');
    147                     if (!$page->IsDeletedFromStage) {
    148                         $page->write();
    149                     }
    150
    151                     // This will syncLinkTracking on published
    152                     Versioned::reading_stage('Live');
    153                     if ($page->ExistsOnLive) {
    154                         $page->write();
    155                     }
    156                 }
    157             }
    158             Versioned::reading_stage($origStage);
    159         }
    160         */
    161     }
    162
    163     /**
    164      * @todo Enforce on filesystem URL level via mod_rewrite
    165      *
    166      * @return boolean
    167      */
    168     function canView($member = null) {
    169         if(!$member) $member = Member::currentUser();
    170
    171         $results = $this->extend('canView', $member);
    172         if($results && is_array($results)) if(!min($results)) return false;
    173
    174         return true;
    175     }
    176
    177     /**
    178      * Returns true if the following conditions are met:
    179      * - CMS_ACCESS_AssetAdmin
    180      *
    181      * @todo Decouple from CMS view access
    182      *
    183      * @return boolean
    184      */
    185     function canEdit($member = null) {
    186         if(!$member) $member = Member::currentUser();
    187
    188         $results = $this->extend('canEdit', $member);
    189         if($results && is_array($results)) if(!min($results)) return false;
    190         if(Permission::checkMember($member, 'CMS_ACCESS_AssetAdmin')) return true;
    191         return $results;
    192     }
    193
    194     /**
    195      * @return boolean
    196      */
    197     function canCreate($member = null) {
    198         if(!$member) $member = Member::currentUser();
    199
    200         $results = $this->extend('canCreate', $member);
    201         if($results && is_array($results)) if(!min($results)) return false;
    202
    203         return $this->canEdit($member);
    204     }
    205
    206     /**
    207      * @return boolean
    208      */
    209     function canDelete($member = null) {
    210         if(!$member) $member = Member::currentUser();
    211
    212         $results = $this->extend('canDelete', $member);
    213         if($results && is_array($results)) if(!min($results)) return false;
    214
    215         return $this->canEdit($member);
    216     }
    217
    218     public function appCategory() {
    219         $ext = $this->Extension;
    220         switch($ext) {
    221             case "aif": case "au": case "mid": case "midi": case "mp3": case "ra": case "ram": case "rm":
    222             case "mp3": case "wav": case "m4a": case "snd": case "aifc": case "aiff": case "wma": case "apl":
    223             case "avr": case "cda": case "mp4": case "ogg":
    224                 return "audio";
    225
    226             case "mpeg": case "mpg": case "m1v": case "mp2": case "mpa": case "mpe": case "ifo": case "vob":
    227             case "avi": case "wmv": case "asf": case "m2v": case "qt": case "mkv":
    228                 return "movie";
    229
    230             case "zip": case "arc": case "rar": case "tar": case "gz": case "tgz": case "bz2": case "dmg": case "jar":
    231             case "ace": case "arj": case "bz": case "cab": case "7z":
    232                 return "zip";
    233
    234             case "bmp": case "gif": case "jpg": case "jpeg": case "pcx": case "tif": case "png": case "alpha":
    235             case "als": case "cel": case "icon": case "ico": case "ps":
    236             case 'dmg': case 'ico': case 'tiff':
    237                 return "image";
    238
    239             case 'xls': case 'ods': case 'xlsx': case 'csv':
    240                 return 'xls';
    241
    242             case 'doc': case 'odt': case 'docx': case 'rtf':
    243                 return 'doc';
    244
    245             case 'txt':
    246                 return 'txt';
    247
    248             case 'pdf':
    249                 return 'pdf';
    250         }
    251     }
    252
    253     function CMSThumbnail() {
    254         $filename = $this->Icon();
    255         $text = _t('File.DOWNLOAD','Download');
    256         return "<div style=\"text-align:center;width: 100px;padding-top: 15px;\"><a target=\"_blank\" href=\"$this->URL\" title=\"$text: $this->URL\"><img src=\"$filename\" alt=\"$filename\" /></a><br /><br /><a style=\"color: #0074C6;\"target=\"_blank\" href=\"$this->URL\" title=\"$text: $this->URL\">$text</a><br /><em>$this->Size</em></div>";
    257     }
    258
    259     /**
    260      * Return the URL of an icon for the file type
    261      */
    262     function Icon() {
    263         $ext = $this->Extension;
    264         if(!Director::fileExists(SAPPHIRE_DIR . "/images/app_icons/{$ext}_32.gif")) {
    265             $ext = $this->appCategory();
    266         }
    267
    268         if(!Director::fileExists(SAPPHIRE_DIR . "/images/app_icons/{$ext}_32.gif")) {
    269             $ext = "generic";
    270         }
    271
    272         return SAPPHIRE_DIR . "/images/app_icons/{$ext}_32.gif";
    273     }
    274
    275     /**
    276      * Should be called after the file was uploaded
    277      */
    278     function onAfterUpload() {
    279         $this->extend('onAfterUpload');
    280     }
    281
    282     /**
    283      * Delete the database record (recursively for folders) without touching the filesystem
    284      */
    285     public function deleteDatabaseOnly() {
    286         if(is_numeric($this->ID)) DB::query("DELETE FROM \"File\" WHERE \"ID\" = $this->ID");
    287     }
    288
    289     /**
    290      * Event handler called before deleting from the database.
    291      * You can overload this to clean up or otherwise process data before delete this
    292      * record.  Don't forget to call parent::onBeforeWrite(), though!
    293      */
    294     protected function onBeforeWrite() {
    295         parent::onBeforeWrite();
    296
    297         if(!$this->Name) $this->Name = "new-" . strtolower($this->class);
    298         $this->FileSize = $this->getAbsoluteSize();
    299     }
    300
    301     /**
    302      * Collate selected descendants of this page.
    303      * $condition will be evaluated on each descendant, and if it is succeeds, that item will be added
    304      * to the $collator array.
    305      * @param condition The PHP condition to be evaluated.  The page will be called $item
    306      * @param collator An array, passed by reference, to collect all of the matching descendants.
    307      */
    308     public function collateDescendants($condition, &$collator) {
    309         if($children = $this->Children()) {
    310             foreach($children as $item) {
    311                 if(!$condition || eval("return $condition;")) $collator[] = $item;
    312                 $item->collateDescendants($condition, $collator);
    313             }
    314             return true;
    315         }
    316     }
    317
    318     /**
    319      * Setter function for Name.
    320      * Automatically sets a default title.
    321      */
    322     function setName($name) {
    323         $oldName = $this->Name;
    324
    325         // It can't be blank
    326         if(!$name) $name = $this->Title;
    327
    328         // Fix illegal characters
    329         $title = $name;
    330         $name = Convert::rus2lat($name);
    331         $name = ereg_replace(' +','-',trim($name));
    332         $name = ereg_replace('[^A-Za-z0-9.+_\-]','',$name);
    333
    334         while(!empty($name) && ($name[0] == '_' || $name[0] == '.')) {
    335             $name = substr($name, 1);
    336         }
    337
    338         // We might have just turned it blank, so check again.
    339         if(!$name) $name = 'new-folder';
    340
    341         // If it's changed, check for duplicates
    342         if($oldName && $oldName != $name) {
    343             if($dotPos = strrpos($name, '.')) {
    344                 $base = substr($name,0,$dotPos);
    345                 $ext = substr($name,$dotPos);
    346             } else {
    347                 $base = $name;
    348                 $ext = "";
    349             }
    350             $suffix = 1;
    351             while(DataObject::get_one("File", "\"Name\" = '" . addslashes($name) . "' AND \"ParentID\" = " . (int)$this->ParentID)) {
    352                 $suffix++;
    353                 $name = "$base-$suffix$ext";
    354             }
    355         }
    356
    357         if(!$this->getField('Title')) $this->__set('Title', str_replace(array('-','_'),' ',ereg_replace('\.[^.]+$','',$title)));
    358         $this->setField('Name', $name);
    359
    360
    361         if($oldName && $oldName != $this->Name) {
    362             $this->resetFilename();
    363         } else {
    364             $this->autosetFilename();
    365         }
    366
    367
    368         return $this->getField('Name');
    369     }
    370
    371     /**
    372      * Change a filename, moving the file if appropriate.
    373      * @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.
    374      */
    375     protected function resetFilename($renamePhysicalFile = true) {
    376         $oldFilename = $this->getField('Filename');
    377         $newFilename = $this->getRelativePath();
    378
    379         if($this->Name && $this->Filename && file_exists(Director::getAbsFile($oldFilename)) && strpos($oldFilename, '//') === false) {
    380             if($renamePhysicalFile) {
    381                 $from = Director::getAbsFile($oldFilename);
    382                 $to = Director::getAbsFile($newFilename);
    383
    384                 // Error checking
    385                 if(!file_exists($from)) user_error("Cannot move $oldFilename to $newFilename - $oldFilename doesn't exist", E_USER_WARNING);
    386                 else if(!file_exists(dirname($to))) user_error("Cannot move $oldFilename to $newFilename - " . dirname($newFilename) . " doesn't exist", E_USER_WARNING);
    387                 else if(!rename($from, $to)) user_error("Cannot move $oldFilename to $newFilename", E_USER_WARNING);
    388
    389                 else $this->updateLinks($oldFilename, $newFilename);
    390
    391             } else {
    392                 $this->updateLinks($oldFilename, $newFilename);
    393             }
    394         } else {
    395             // If the old file doesn't exist, maybe it's already been renamed.
    396             if(file_exists(Director::getAbsFile($newFilename))) $this->updateLinks($oldFilename, $newFilename);
    397         }
    398
    399         $this->setField('Filename', $newFilename);
    400     }
    401
    402     /**
    403      * Set the Filename field without manipulating the filesystem.
    404      */
    405     protected function autosetFilename() {
    406         $this->setField('Filename', $this->getRelativePath());
    407     }
    408
    409     function setField( $field, $value ) {
    410         parent::setField( $field, $value );
    411     }
    412
    413     /**
    414      * Rewrite links to the $old file to now point to the $new file
    415      */
    416     protected function updateLinks($old, $new) {
    417         if(class_exists('Subsite')) Subsite::disable_subsite_filter(true);
    418
    419         if($tracking = $this->BackFileTracking()) {
    420             foreach($tracking as $entry) {
    421                 if (($page = $entry->LinkedObject()) && $page->hasMethod('rewriteFileURL')) {
    422                     $page->rewriteFileURL($old,$new);
    423                 }
    424             }
    425         }
    426
    427         if(class_exists('Subsite')) Subsite::disable_subsite_filter(false);
    428     }
    429
    430     function setParentID($parentID) {
    431         $this->setField('ParentID', $parentID);
    432
    433         if($this->Name) $this->resetFilename();
    434         else $this->autosetFilename();
    435
    436         return $this->getField('ParentID');
    437     }
    438
    439     /**
    440      * Gets the absolute URL accessible through the web.
    441      *
    442      * @uses Director::absoluteBaseURL()
    443      * @return string
    444      */
    445     function getAbsoluteURL() {
    446         return Director::absoluteBaseURL() . $this->getFilename();
    447     }
    448
    449     /**
    450      * Gets the relative URL accessible through the web.
    451      *
    452      * @uses Director::baseURL()
    453      * @return string
    454      */
    455     function getURL() {
    456                 if(Object::has_extension('SiteTree', 'SiteTreeSubsites')){
    457                     return Director::absoluteURL('/') . $this->getFilename();
    458                 }
    459         return Director::baseURL() . $this->getFilename();
    460     }
    461
    462     /**
    463      * Return the last 50 characters of the URL
    464      */
    465     function getLinkedURL() {
    466         return "$this->Name";
    467     }
    468
    469     function getFullPath() {
    470         $baseFolder = Director::baseFolder();
    471
    472         if(strpos($this->getFilename(), $baseFolder) === 0) {
    473             // if path is absolute already, just return
    474             return $this->getFilename();
    475         } else {
    476             // otherwise assume silverstripe-basefolder
    477             return Director::baseFolder() . '/' . $this->getFilename();
    478         }
    479     }
    480
    481     /**
    482      * Returns
    483      *
    484      * @return String
    485      */
    486     function getRelativePath() {
    487         if($this->ParentID) {
    488             $p = DataObject::get_by_id('Folder', $this->ParentID);
    489             if($p && $p->exists()) return $p->getRelativePath() . $this->getField("Name");
    490             else return ASSETS_DIR . "/" . $this->getField("Name");
    491         } else if($this->getField("Name")) {
    492             return ASSETS_DIR . "/" . $this->getField("Name");
    493         } else {
    494             return ASSETS_DIR;
    495         }
    496     }
    497
    498     function DeleteLink() {
    499         return Director::absoluteBaseURL()."admin/assets/removefile/".$this->ID;
    500     }
    501
    502     function getFilename() {
    503         if($this->getField('Filename')) {
    504             return $this->getField('Filename');
    505         } else {
    506             return ASSETS_DIR . '/';
    507         }
    508     }
    509
    510     function setFilename($val) {
    511         $this->setField('Filename', $val);
    512         $this->setField('Name', basename($val));
    513     }
    514
    515     /*
    516      * FIXME This overrides getExtension() in DataObject, but it does something completely different.
    517      * This should be renamed to getFileExtension(), but has not been yet as it may break
    518      * legacy code.
    519      */
    520     function getExtension() {
    521         return self::get_file_extension($this->getField('Filename'));
    522     }
    523
    524     /**
    525      * Gets the extension of a filepath or filename,
    526      * by stripping away everything before the last "dot".
    527      *
    528      * @param string $filename
    529      * @return string
    530      */
    531     public static function get_file_extension($filename) {
    532         return strtolower(substr($filename,strrpos($filename,'.')+1));
    533     }
    534
    535     /**
    536      * Return the type of file for the given extension
    537      * on the current file name.
    538      *
    539      * @return string
    540      */
    541     function getFileType() {
    542         $types = array(
    543             'gif' => 'GIF image - good for diagrams',
    544             'jpg' => 'JPEG image - good for photos',
    545             'jpeg' => 'JPEG image - good for photos',
    546             'png' => 'PNG image - good general-purpose format',
    547             'ico' => 'Icon image',
    548             'tiff' => 'Tagged image format',
    549             'doc' => 'Word document',
    550             'xls' => 'Excel spreadsheet',
    551             'zip' => 'ZIP compressed file',
    552             'gz' => 'GZIP compressed file',
    553             'dmg' => 'Apple disk image',
    554             'pdf' => 'Adobe Acrobat PDF file',
    555             'mp3' => 'MP3 audio file',
    556             'wav' => 'WAV audo file',
    557             'avi' => 'AVI video file',
    558             'mpg' => 'MPEG video file',
    559             'mpeg' => 'MPEG video file',
    560             'js' => 'Javascript file',
    561             'css' => 'CSS file',
    562             'html' => 'HTML file',
    563             'htm' => 'HTML file'
    564         );
    565
    566         $ext = $this->getExtension();
    567
    568         return isset($types[$ext]) ? $types[$ext] : 'unknown';
    569     }
    570
    571     /**
    572      * Returns the size of the file type in an appropriate format.
    573      */
    574     function getSize() {
    575         $size = $this->getAbsoluteSize();
    576
    577         return ($size) ? self::format_size($size) : false;
    578     }
    579
    580     public static function format_size($size) {
    581         if($size < 1024) return $size . ' bytes';
    582 //      if($size < 1024*10) return (round($size/1024*10)/10). ' KB';
    583         if($size < 1024*1024) return round($size/1024) . ' KB';
    584         if($size < 1024*1024*10) return (round(($size/1024)/1024*10)/10) . ' MB';
    585         if($size < 1024*1024*1024) return round(($size/1024)/1024) . ' MB';
    586         return round($size/(1024*1024*1024)*10)/10 . ' GB';
    587     }
    588
    589     /**
    590      * Return file size in bytes.
    591      * @return int
    592      */
    593     function getAbsoluteSize(){
    594         if ($this->FileSize) {
    595             return $this->FileSize;
    596         }
    597         if(file_exists($this->getFullPath())) {
    598             $size = filesize($this->getFullPath());
    599             return $size;
    600         } else {
    601             return 0;
    602         }
    603     }
    604
    605     /**
    606      * We've overridden the DataObject::get function for File so that the very large content field
    607      * is excluded!
    608      *
    609      * @todo Admittedly this is a bit of a hack; but we need a way of ensuring that large
    610      * TEXT fields don't stuff things up for the rest of us.  Perhaps a separate search table would
    611      * be a better way of approaching this?
    612      * @deprecated alternative_instance_get()
    613      */
    614     public function instance_get($filter = "", $sort = "", $join = "", $limit="", $containerClass = "DataObjectSet", $having="") {
    615         $query = $this->extendedSQL($filter, $sort, $limit, $join, $having);
    616         $baseTable = reset($query->from);
    617
    618         $excludeDbColumns = array('Content');
    619
    620         // Work out which columns we're actually going to select
    621         // In short, we select everything except File.Content
    622         $dataobject_select = array();
    623         foreach($query->select as $item) {
    624             /*
    625             if($item == "\"File\".*") {
    626                 $fileColumns = DB::query("SHOW FIELDS IN \"File\"")->column();
    627                 $columnsToAdd = array_diff($fileColumns, $excludeDbColumns);
    628                 foreach($columnsToAdd as $otherItem) $dataobject_select[] = '"File".' . $otherItem;
    629             } else {
    630             */
    631                 $dataobject_select[] = $item;
    632             //}
    633         }
    634
    635         $query->select = $dataobject_select;
    636
    637         $records = $query->execute();
    638         $ret = $this->buildDataObjectSet($records, $containerClass);
    639         if($ret) $ret->parseQueryLimit($query);
    640
    641         return $ret;
    642     }
    643
    644     public function flushCache() {
    645         parent::flushCache();
    646
    647         self::$cache_file_fields = null;
    648     }
    649
    650     /**
    651      *
    652      * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
    653      *
    654      */
    655     function fieldLabels($includerelations = true) {
    656         $labels = parent::fieldLabels($includerelations);
    657         $labels['Name'] = _t('File.Name', 'Name');
    658         $labels['Title'] = _t('File.Title', 'Title');
    659         $labels['Filename'] = _t('File.Filename', 'Filename');
    660         $labels['Content'] = _t('File.Content', 'Content');
    661         $labels['Sort'] = _t('File.Sort', 'Sort Order');
    662
    663         return $labels;
    664     }
    665
    666     function validate() {
    667         if(File::$apply_restrictions_to_admin || !Permission::check('ADMIN')) {
    668             $extension = strtolower(pathinfo($this->Name, PATHINFO_EXTENSION));
    669
    670             if($extension && !in_array($extension, self::$allowed_extensions)) {
    671                 $exts =  self::$allowed_extensions;
    672                 sort($exts);
    673                 $message = sprintf(
    674                     _t(
    675                         'File.INVALIDEXTENSION',
    676                         'Extension %s is not allowed (valid: %s)',
    677                         PR_MEDIUM,
    678                         'Argument 1: Invalid extension, Argument 2: Comma-separated list of valid extensions'
    679                     ),
    680                     $extension,
    681                     implode(', ',$exts)
    682                 );
    683                 return new ValidationResult(false, $message);
    684             }
    685         }
    686         return new ValidationResult(true);
    687     }
    688
    689     /**
    690      * Allow custom fields for uploads in {@link AssetAdmin}.
    691      * Similar to {@link getCMSFields()}, but a more restricted
    692      * set of fields which can be reliably set on any file type.
    693      *
    694      * Needs to be enabled through {@link AssetAdmin::$metadata_upload_enabled}
    695      *
    696      * @return FieldSet
    697      */
    698     function uploadMetadataFields() {
    699         $fields = new FieldSet();
    700         $fields->push(new TextField('Title', $this->fieldLabel('Title')));
    701         $this->extend('updateUploadMetadataFields', $fields);
    702
    703         return $fields;
    704     }
    705
    706     // Quantity of pages on which this file is used
    707     function getUsageCount() {
    708         $count = 0;
    709         // from FileDataObjectTrackingDecorator
    710         if ($this->hasMethod('BackFileTracking') && ($links = $this->BackFileTracking())) {
    711             $count += $links->Count();
    712         }
    713         return $count;
    714     }
    715 }
    716
    717 ?>
    718

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -